哈希表是一个怎样的数据结构呢
我的理解是整体上来看,哈希表是一个数组,但是数组的每一项不只有一项,而是有很多项又组成一个数组,我们也可以想象成一个数组里每一项都是一个水桶
为什么要设计这样的哈希表结构呢?
那我们就先要讲讲数组的弊端,数组其实在我们的使用中很是便利了,但是当数据庞大到几千万的量级上时,劣势就很明显了(因为数组时临时存放在内存中的,这里补充一个知识点,你知道两千万大小的数组占的内存大小是多少吗?先卖个关子)
回到数组的弊端,数组在有下标值的情况下查找的效率时很高的,O(1)就能解决;举个栗子吧,如果我们有一个数组,数组每一项都有某一个同学的家庭地址,现在我们打算要去小天家里玩,但是呢,我们只知道小天的名字,但是我们不知道小天住在哪里,这个时候只能通过数组的遍历,一个个的对不查找到的是不是小天的家庭地址,所以如果我们要找到小天,起码要对比四次;想象一下,如果这个数组存放的是全校同学的家庭地址,那我们需要做的工作就多很多了
所以设计出哈希表的初衷是为了提高数组的查找效率,无论多少的数据,插入,删除,查找的操作都接近常量的事件,最好能达到O(1)
那哈希表是怎么实现的呢
哈希表主要的实现是通过哈希函数,将要查找的信息转换成数字,并讲这个数字作为存放在数组里位置的索引,需要查找的时候,通过这个下标去查找
在哈希函数里,你可以自己设置成你想要的映射规则,有很多中方式,比如把信息转换成对应的ASCII编码,或者设计一个自己的编码系统,有了编码系统,还需要组合方式得出一个数字,这里的方式也多样,你可以选择把所有字符转换的数字求和、幂的来连乘等等
这里幂的连乘,涉及到霍纳准则,霍纳准则是通过提取同类项的方法实现了乘法运算的降维
这样你就会得到一个很大的编码后的数字索引,这时候就需要进行压缩的操作了,因为这样的编码方式,面对只有几个信息的时候,是很浪费内存空间的,比如 有 [1 5 29 9] 这样一个数组,9进行编码后 (编码方式是幂的连乘)9*37+9=384,这样转换的下标就是384,但是数组的数据实际最多只有四个,所以光是幂的连乘还不够,需要压缩。伟大的数学家已经找到了很好的办法,那就是进行霍纳准则后取余,使用霍纳准则得到一个大的数字,在进行取余压缩,因为取余操作的结果的范围(0-9),当数据很多的时候,冲突的情况是必然会发生的
hash(str, size) { //hash(9,11)
let hashCode = 0;
for (let i = 0; i < str.length; i++) {
hashCode = 37 * hashCode + str.charCodeAt(i);
}
//一般采用一个质数37相乘,为什么呢?我的理解是,这可以让我们最后生成的索引值分散,减少冲突的可能,我后面会说冲突的具体情况
hashCode = hashCode % size;
return hashCode;
}
什么是冲突呢?
冲突是指两个信息转换成的数字是一样的,又因为哈希表是不可以存放重复的key(信息)的,那这样怎么实现一个索引能有多个信息呢,有两个方法,链地址法和开放地址法,我这里主要介绍常用的链地址法,看到图片你大概就懂了这个方法的含义了
就是给每个下标值对应的位置,都设计为数组的结构,这种方法也会有弊端,可能某一项的数组特别多,这样就会失衡,但是也没什么坏处吧,在数组里采用的是线性的查找方式,这种方式想必大家都很熟悉了
下面我们就来实现一下哈希表的操作吧(哈希表的难点主要在理论哈,只要思路清晰,代码还是很好实现的)
封装哈希表结构【ES6实现】
class HashTable {
constructor() {
this.storage = []; //定义存储的数组表
this.count = 0; //记录已经存放的数据量
this.limit = 7; //定义数组的长度
}
}
哈希函数
//哈希函数 霍纳算法
hash(str, size) {
let hashCode = 0;
for (let i = 0; i < str.length; i++) {
hashCode = 37 * hashCode + str.charCodeAt(i);
}
hashCode = hashCode % size;
return hashCode;
}
这里的37可以换成你想要的质数,一般为31、37、43、41...
添加修改操作
//添加或者修改操作
put(key, value) {
let index = this.hash(key, this.limit);//利用hash函数处理信息key得到索引值
let bucket = this.storage[index];
//判断桶为空的情况
if (bucket == null) {
bucket = []; //定义每一项都是一个数组
this.storage[index] = bucket;
}
//判断是否要修改值
for (let i = 0; i < bucket.length; i++) {
let tuple = bucket[i];
if (tuple[0] == key) {
tuple[1] = value;
}
}
//增加操作
bucket.push([key, value]); //存放的形式类似python的元组
this.count += 1;
}
查询操作
//查询操作
find(key) {
let index = this.hash(key, this.limit);
let bucket = this.storage[index];
if (bucket == null) return null;
for (let i = 0; i < bucket.length; i++) {
let tuple = bucket[i];
if (tuple[0] == key) {
return tuple[1];
}
}
return false;
}
删除操作
//删除操作
delete(key) {
let index = this.hash(key, this.limit);
let bucket = this.storage[index];
if (bucket == null) return null
for (let i = 0; i < bucket.length; i++) {
let tuple = bucket[i];
if (tuple[0]==key){
bucket.splice(i,1);
this.count-=1;
return tuple[1];
}
}
return "抱歉,哈希表不存在" + key;
}
其他方法
//判断哈希表是否为空
isEmpty(){
return this.count==0;
}
//哈希表大小
size(){
return this.count;
}
讲的还没有很全面,这部分的理论知识相对其他的结构多一些,文章如有不对的地方,欢迎批评指正!
参考: