一、哈希函数
哈希函数:函数里面封装了将字符串转化为对应的index的具体实现;
我们要将字符串转化为对应的index,首先得知道如何区分字符串,一般我们采用编码方案,也就是每一个字符都有自己对应的唯一的编码:
现成的就是unicode(万国码),unicode是字符集没有规定如何存储对应的编码。unicode的实现有UTF-16、UTF-8、UTF-32,这里我们采用UTF-16;而js也给我们封装了对应的API:
charCodeAt() //返回0·65535之间的整数,表示给定索引处的UTF-16代码单元;
有了对应的字符的编码,新的问题来了,我们要如何区分每个字符串呢?
编码相加吗?这是不行的,容易重复;
幂的连乘?这个可以的,可以基本保证唯一性,但是我们对算法做出优化,因为直接加乘效率比较低,我们可以采用霍纳算法;
还有一个问题:使用幂的连乘之后,index过于巨大,很消耗性能,我们得想办法将index缩小或者说映射到小一点的index上;如何实现了,这里可以采用取模;用于取模的除数最好是质数,可以让index的分布更均与;
以上就是哈希函数的实现思路,代码实现:
第一个参数是需要映射的字符串,第二个是哈希表的长度(用于去模)
hashTabel.prototype.hashFuns = function (str, size) {
// 定义hashcode变量
let hashcode = 0;
// 类似霍纳算法
for (let i = 0; i < str.length; i++) {
hashcode = 37 * hashcode + str.charCodeAt(i);
}
// 取余(模)操作
let index = hashcode % size;
return index;
};
二、index冲突的解决
那我们有了哈希函数后映射出来的index是唯一的吗?
其实不是,也会起冲突的,有两种解决办法:
链地址法:就是存在冲突,我们就用链表或数组将他们放在一起,找的时候,遍历一下即可;
存取或探测效率随填装因子(元素总数/哈希表长度)的增大而平稳增大;
开放地址法:存在冲突,就去寻找空白的位置来放置数据;
存取或探测效率随填装因子(元素总数/哈希表长度)的增大而增大;前0.25效率很高,后0.75效率低;
这里我们采用链地址法,用数组存放冲突元素,数组的名字叫-bucket,里面的内容叫-tuple;
三、实现哈希表
有了哈希函数后,哈希表的实现就很简单了,就是创建数组,根据对应的index去数组里找bucket,再遍历bucket拿到元素或放入新元素即可;
四、具体实现
function hashTabel() {
// 属性
this.storage = [];
this.count = 0;
this.limit = 7;
// 方法
// 1.哈希函数
hashTabel.prototype.hashFuns = function (str, size) {
// 定义hashcode变量
let hashcode = 0;
// 霍纳算法
for (let i = 0; i < str.length; i++) {
hashcode = 37 * hashcode + str.charCodeAt(i);
}
// 取余(模)操作
let index = hashcode % size;
return index;
};
// 插入,修改操作
hashTabel.prototype.put = function (key, value) {
// 1.根据key,获取对应的idnex
let index = this.hashFuns(key, this.limit);
// 2.桶bucket
let bucket = this.storage[index];
if (!bucket) {
bucket = [];
this.storage[index] = bucket;
}
// 判断桶里面有没有对应的数组
for (let index = 0; index < bucket.length; index++) {
const tuple = bucket[index];
if (tuple[o] == key) {
tuple[1] = value;
return;
}
}
// 进行添加数据
bucket.push([key, value]);
// 别忘了count++
this.count++;
// 判断是否需要扩容操做
if (this.count > this.limit * 0.75) {
this.resize(this.limit * 2);
}
};
// 获取操作
hashTabel.prototype.get = function (key) {
let index = this.hashFuns(key, this.limit);
if (!this.storage[index]) return null;
let bucket = this.storage[index];
for (let index = 0; index < bucket.length; index++) {
const tuple = bucket[index];
if (tuple[0] == key) return tuple[1];
}
return null;
};
// 删除方法
hashTabel.prototype.remove = function (key) {
let index = this.hashFuns(key, this.limit);
if (!this.storage[index]) return false;
let bucket = this.storage[index];
for (let index = 0; index < bucket.length; index++) {
const tuple = bucket[index];
if (tuple[0] == key) {
bucket.splice(index, 1);
this.count--;
// 判断是否需要减容
if (this.limit > 7 && this.count < this.limit * 0.25) {
this.resize(Math.floor(this.limit / 2));
}
return tuple[1];
}
}
return null;
};
// 其他方法
hashTabel.prototype.isEmpty = function () {
return this.count == 0;
};
hashTabel.prototype.size = function () {
return this.count;
};
hashTabel.prototype.resize = function (newLimit) {
// 保留原数据
let oldstorage = this.storage;
// 重置属性
this.storage = [];
this.count = 0;
this.limit = newLimit;
// 以前数据遍历放入
for (let index = 0; index < oldstorage.length; index++) {
if (!oldstorage[index]) continue;
const bucket = oldstorage[index];
for (let i = 0; i < bucket.length; i++) {
let tuple = bucket[i];
this.put(tuple[0], tuple[1]);
// this.count++;put有++
}
}
};
}
对了也可以给哈希表扩容的,这里就不实现了哈,拜拜