散列函数(Hash):Hash,一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
散列表的JavaScript简单实现:
put(key, value): 向散列表中增加一个新的项
remove(key):根据键值从散列表中移除一个值
get(key):返回根据键值检索的特定的值
function HashTable() {
let table = [];
let loseloseHashCode = function (key) {
let hash = 0;
for(let i = 0; i < key.length; i++){
hash += key.charCodeAt(i);
}
return hash % 37;
};
this.put = function (key, value) {
let position = loseloseHashCode(key);
console.log(position + '-' + key);
table[position] = value;
};
this.get = function (key) {
return table[loseloseHashCode(key)];
};
this.remove = function (key) {
table[loseloseHashCode(key)] = undefiend;
};
}
但是不同的输入可能会在散列表中对应相同的位置,则为冲突。解决冲突的方法主要有分离链接、线性探查和双散列法
分离链接法:为散列表的每个位置创建一个链表并将元素储存在链表里面,这是解决冲突的最简单的方法,但是这种方法在散列表实例之外还需要额外的储存空间。
使用分离链接法重写散列表的冲突得到新的散列表函数。以下:
function HashTable() {
let table = [];
let loseloseHashCode = function (key) {
let hash = 0;
for(let i = 0; i < key.length; i++){
hash += key.charCodeAt(i);
}
return hash % 37;
};
let ValuePair = function (key, value) {
this.key = key;
this.value = value;
this.toString = function () {
return '[' + this.key + '-' + this.value + ']';
}
};
//重写put方法
this.put = function (key, value) {
let position = loseloseHashCode(key);
if(table[position] === undefined){
table[position] = new LinkedList();
}
table[position].append(new ValuePair(key, value));
};
//get 方法
this.get = function (key) {
let position = loseloseHashCode(key);
if(table[position] !== undefiend){
//遍历链表寻找键/值
let current = table[position].getHead(); // 获取链表表头的引用
while(current.next){
if(current.element.key === key){
return current.element.value;
}
current = current.next;
}
//检查元素在链表第一个或最后一个节点的情况
if(current.element.key === key){
return current.element.value;
}
}
return undefined;
};
//remove方法
this.remove = function (key) {
let position = loseloseHashCode(key);
if(table[position] !== undefined){
let current = table[position].getHead();
while(current.next){
if(current.element.key === key){
table[position].remove(current.element);
if(table[position].isEmpty()){
table[position] = undefined;
}
return true;
}
current = current.next;
}
//检查是否为第一个或者最后一个元素
if(current.element.key === key){
table[position].remove(current.element);
if(table[position].isEmpty()){
table[position] = undefined;
}
return true;
}
}
return false;
};
}
线性探查法:当想向散列表中加入一个元素的时候,如果索引为index的位置已经被占据,就尝试index+1 的位置,如果index+1的位置也被占据了,就尝试index+2 的位置,依次类推,直到可以放下为止。
function HashTable() {
let table = [];
let loseloseHashCode = function (key) {
let hash = 0;
for(let i = 0; i < key.length; i++){
hash += key.charCodeAt(i);
}
return hash % 37;
};
let ValuePair = function (key, value) {
this.key = key;
this.value = value;
this.toString = function () {
return '[' + this.key + '-' + this.value + ']';
}
};
//使用线性探查法处理冲突
this.put = function (key, value) {
let position = loseloseHashCode(key);
if(table[position] === undefiend){
table[position] = new ValuePair(key, value);
}else{
let index = ++position;
while(table[index] !== undefined){
index++;
}
table[index] = new ValuePair(key, value);
}
};
this.get = function (key) {
let position = loseloseHashCode(key);
if(table[position] !== undefined){
if(table[position].key === key){
return table[position].value;
}else{
let index = ++position;
while(table[position] === undefined || table[index].key !== key){
index++;
}
if(table[index].key === key){
return table[index].value;
}
}
}
return undefined;
};
this.remove = function (key) {
let position = loseloseHashCode(key);
if(table[position] !== undefined){
if(table[position].key === key){
table[index] = undefined;
}else{
let index = ++position;
while(table[position] === undefined || table[index].key !== key){
index++;
}
if(table[index].key === key){
table[index] = undefined;
}
}
}
return undefined;
};
这里我们使用的散列函数是 loseloseHashCode ,遍历key并将从ASCII表中查到的每个字符对应的ASCII值加到一起,为得到较小的值,取余37(一般为质数,减少冲突);
更好一点的散列函数如下:
let djb2HashCode = function (key) {
let hash = 5381;
for(let i = 0; i < key.length; i++){
hash = hash * 33 + key.charCodeAt(i);
}
return hash % 1013; //与一个质数作取余运算得到较小的值
};