散列
1 什么是散列?
散列是一种常用的数据存储技术,散列后的数据可以快速插入或取用,相应的数据结构叫做散列表。在散列表上插入、删除、取用数据都非常快,但是对于查找操作来说却效率底下。
2 散列的实现
基于数组设计,数组长度预先设定。使用散列表存储数据时,通过一个散列函数将键映射为一个数字,这个数字的范围是0到散列表的长度。键的数量无限的,数组长度有限,出现的两个键映射为同一个值的情况成为碰撞。常见的限制是数组长度为一个质数。
3 HashTable类
function HashTable(){
this.table=new Array(137);
this.simpleHash=simpleHash;
this.showDistro=showDistro;
this.put=put;
}
3.1 选择一个散列函数
散列函数的选择依赖于键值的数据类型,如果键是整形,最简单的散列函数就是以数组的长度对键取余,称为除留余数法。对于键是字符串类型的情况,将每个字符的ASCII值相加是可供参考的。
//散列函数
function simpleHash(data){
var total=0;
for(var i=0;i<data.length;i++){
total+=data.charCodeAt(i);
}
return total%this.table.length;
}
//将数据存入散列表
function put(data){
var pos=this.simpleHash(data);
this.table[pos]=data;
}
//显示数据
function showDistro(){
var n=0;
for(var i=0;i<this.table.length;i++){
if(this.table[i]!=undefined){
console.log(i+":"+this.table[i]);
}
}
}
3.2 一个更好的散列函数
使用霍纳算法,仍然先计算字符串中各个字符的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;
return parseInt(total);
}
测试发现无论是对于字符串还是整兴,betterHash()的散列效果都更胜一筹。
function get(key){
return this.table[this.betterHash(key)];
}
4 碰撞处理
介绍两种碰撞解决办法,开链法和线性探测法。
4.1 开链法
指实现散列表的底层数组中,每个数组元素又是一个新的数据结构,比如另一个数组,这样就能存储多个键了。第二组数组也被成为链。
function buildChains(){
for(var i=0;i<this.table.length;i++){
this.table[i]=new Array();
}
}
修改后的put()方法:既保存数据也保存键值,使用链中两个连续的单元格,第一个保存键值第二个保存数据。
修改后的get()方法:先对键值散列,根据散列后的值找到散列表中的相应位置,然后搜索该位置上的链,直到找到键值。如果找到就将其后的数据返回,否则返回undefined。
function put(key,data){
var index=0;
var pos=this.betterHash(key);
while(this.table[pos][index]!=undefined){
index++;
}
this.table[pos][index]=key;
this.table[pos][++index]=data;
}
function get(key){
var index=0;
var pos=this.betterHash(key);
while(this.table[pos][index]!=undefined&&this.table[pos][index]!=key){
index+=2;
}
return this.table[pos][index+1];
}
4.2 线性探测法
线性探测法隶属于一种更一般化的散列技术:开放寻址散列。当发生碰撞时,检查散列表中下一个位置是否为空,如果为空,就将数据存入该位置,如果不为空,则继续检查下一个位置,知道找到一个空位置为止。该技术基于一个事实:每个散列表都会有很多空的单元格可以用于存储数据。
注意:常常按照以下方式选择解决办法-数组大小是待存储数据个数的1.5倍,使用开链法,数组大小是待存储数据的两倍及以上时,使用线性探测法。
//在HashTable构造函数中加入this.values=[]
function put(key,data){
var pos=this.betterHash(key);
while(this.table[pos]!=undefined){
pos++;
}
this.table[pos]=key;
this.values[pos]=data;
}
function get(key){
var hash=this.betterHash(key);
while(this.table[hash]!=key&&this.table[hash]!=key){
hash++;
}
return this.values[hash];
}