C8 -- 散列
散列使用的数据结构叫做散列表。在散列表上插入、删除和取用数据都非常快,但查找操作效率低下。我们的散列表是基于数组进行设计的。使用散列表存储数据时,通过一个散列函数将键映射为一个数字,这个数字是0到散列表的长度。理想情况下,散列函数会将每个键值映射为一个唯一的数组索引。然而,键的数量是无限的,数组的长度是有限的,应该让散列函数尽量将键均匀地映射到数组中。存在将两个键映射成同一个值的可能,这种现象被称为 :碰撞。
散列表中的数组大小的限制,数组的长度应该是一个质数。确定数组大小的策略,都基于处理碰撞的技术。
1.HashTable类
function HashTable(){
this.table = new Array(137);
this.simpleHash = simpleHash;
this.showDistro = showDistro;
this.put = put;
//this.get = get;
}
function put(data){
var pos = this.simpleHash(data);
this.table[pos] = data;
}
function simpleHash(data){
var total = 0;
for(var i = 0;i<data.length;++i){
total += data.charCodeAt(i);
}
return total % this.table.length;
}
function showDistro(){
var n = 0;
for(var i = 0;i<this.table.length;++i){
if(this.table[i] != undefined){
print(i+":" +this.table[i]);
}
}
}
除留余数法:如果键是整型,最简单的散列函数就是以数组的长度对键取余。(数组的长度要求是质数)
如果键是字符串类型,将字符串中每个字符的ASCII码值相加除留余数。
一个更好的散列函数
为了避免碰撞,首先要确保散列表中用来存储数据的数组其大小是个质数。
霍纳算法:
function betterHash(string,arr){
const H = 37;
var total = 0;
for(var i = 0;i<string.length;++i){
total += H * total + string.charCodeAt(i);
}
total = total % arr.length;
if(total < 0){
total += arr.length-1;
}
return parseInt(total);
}
对散列表排序、从散列表中取值
function put (key,data){
var pos = this.betterHash(key);
this.table[pos] = data;
}
function get(key){
return this.table[this.betterHash(key)];
}
碰撞处理
1.开链法
二维数组
function buildChains(){
for(var i = 0;i<this.table.length;++i ){
this.table[i] = new Array();
}
}
function showDistro(){
var n = 0;
for (var i=0;i<this.table.length;++i){
if(this.table[i][0] != undefined){
print(i+":" +this.table[i]);
}
}
}
//既保存数据也保存键值
function put(key,data){
var pos =this.betterHash(key);
var index = 0;
if(this.table[pos][index] == undefined){
this.table[pos][index+1] = data;
}
++index;
else{
while (this.table[pos][index] != undefined){
++index;
}
this.table[pos][index+1] = data;
}
}
function get(key){
var index =0;
var hash = this.betterHash(key);
if(this.table[pos][index] = key){
return this.table[pos][index+1];
}
index +=2;
else{
while(this.table[pos][index] != key){
index +=2;
}
return this.table[pos][index+1];
}
return undefined;
}
2.线性探测法
如果数组的大小是待存储数据个数的1.5倍,那么使用开链法。如果数组的大小是待存储数据的两倍及两倍以上,那么使用线性探测法。开放寻址散列,当发生碰撞时,线性探测法检查散列表中下一个位置是否为空。如果为空,就将数据存入该位置;如果不为空就继续检查下一个位置,直到找到一个空的位置为止。
数组table和values并行工作,当将一个键值保存到数组table中时,将数据存入数组values中对应的位置。
function put(key,data){
var pos = this.betterHash(key);
if(this.table[pos] == undefined){
this.table[pos] =key;
this.values[pos] = data;
}
else{
while(this.table[pos] != undefined){
pos++;
}
this.table[pos] =key;
this.values[pos] = data;
}
}
function get(key){
var hash = -1;
hash = this.betterHash(key);
if(hash>-1){
for(var i = hash;this.table[hash] !=undefined;i++){
if(this.table[hash] == key){
return this.values[hash];
}
}
}
return undefined;
}