哈希表(也叫关联数组)一种通用的数据结构,哈希表是一种通过关键码去寻找值得数据映射结构
例:新华字典。如果我想知道“按”的详细信息,根据拼音去查找拼音索引,首先查找"an"在字典中的位置,如图所示,就会找到“安”,这个过程就是键码映射,通过key去查找f(key)。其中,安就是关键字(key),f()就是字典索引,也就是哈希函数,查到的页码4就是哈希值。
通过例子可以知道:哈希表是一种通过哈希函数将特定的键映射到特定值的一种数据结构,它维护着键和值之间的一一对应关系。
键(key):又称为关键字。唯一标识要存储的数据,可以是数据本身或者数据的一部分。
槽(slot/bucket):哈希表中用于保存数据的一个单元,也就是数据真正存放的容器。
哈希函数(hash function):将键映射到数据应该存放的槽所在位置的函数。
哈希冲突(hash collision):哈希函数将两个不同的key映射到同一个索引。
列举一个哈希函数:
int h(int x){
return (x%5);
}
哈希表是一个时间和空间做权衡的数据结构。如果没有内存限制,那么可以直接将键作为数组的索引,此时所有查找的时间复杂度是O(1);如果没有时间限制,将键无序存放,查找是顺序查找,时间复杂度是O(n)。而哈希表是在少量空间上实现约为O(1)时间复杂度的查找。
如何构造哈希函数呢?
哈希函数的构造要能够让数据均匀地哈希到槽内,尽量少的减少哈希冲突,哈希函数的构造显得尤为重要:
1、直接寻址法
取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数)
在使用时,为了使哈希地址与存储空间吻合,可以调整a和b…
特点是:哈希函数简单,并且对于不同的关键字不会产生冲突,但是实际问题中,由于关键字集中的元素很少且是连续的,会造成空间的大量浪费,也挺少用的。
2、 数字分析法
假设有一组关键字,每个关键字由几位数字组成,如K1 K2 K3…Kn。是从中提取数字分布比较均匀的若干位作为哈希地址。
例如:对于关键字k1到k8的序列{100011211
100011322 100011413 100011556 100011613 100011756 100011822 100011911} ,可以取第6和第7位作为哈希地址, H(K1)=12
H(K2)=13 H(K3)=14 H(K4)=15 H(K5)=16 H(K6)=17 H(K7)=18 H(K8)=19 。
3、 平方取中法
是取关键字平方的中间几位作为散列地址的方法,具体取多少位看情况,即:H(Ki)=“Ki的平方的中间几位“这也是常用的较好的设计哈希函数的方法。关键字平方后使得它的中间几位和组成关键字的多位值均有关,从而使哈希地址的分布更为均匀,减少冲突的可能性。
4、 折叠法
是首先把关键字分割成位数相同的几段(最后一段位数可少些),段的位数取决于哈希地址的位数,由实际情况而定,然后将他们叠加和(舍去最高进位)作为哈希地址的方法。与平方取中法类似,折叠法也使得关键字的各位值都对哈希地址产生影响。
5、 除留余数法
取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p,p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。
例如上面的示图,5换成2呢,冲突更严重,所以p的选取很重要。
对不同的关键字可能得到同一散列地址,即k1!=k2,而f(k1)=f(k2),这种现象称为碰撞(英语:Collision),也叫哈希冲突。
解决方法:
1、链地址法:
当出现哈希冲突时,在冲突的地方通过链表来解决
这个图中h(x) = x mod 10 ;是一个哈希函数,可见代入x=91和x=1的时候都会到映射到键值为1的slot,那么这样就会引发冲突,在链地址法中,用链表延展去存储同键值的数据。
优点:
- 处理冲突简单,无堆积现象,非同义词不会产生冲突,平均查找时间短
- 在用链地址构造的散列表中,删除结点的操作易于实现
缺点:
哈希冲突比较大的时候(卡槽大小为10,存放10000数据,每个卡槽均1000数据。 极端情况下所有数据在一个卡槽…退化成链表)
2、线性探测法
通过散列函数hash(key),找到关键字key在线性序列中的位置,如果当前位置已经有了一个关键字,就产生了哈希冲突,就往后探测i个位置(i小于线性序列的大小),直到当前位置没有关键字存在。
添加89 ,18,49,58,9
给定哈希函数f(x)=x mod 10
添加89:f(89)=89 mod 10 ==9,当前89存储在9号卡槽位置
添加18:f(18)=18 mod 10 ==8,当前18存储在8号卡槽位置
添加49:f(49)=49 mod 10 ==9,产生了冲突,往后找到第一个空闲位置插入:将49就插入到0号位置
添加58:f(58)=58 mod 10 ==8,产生了冲突,经过多轮探测找到1号位置
Probing探测
p(k,i)探测函数。其值为第i次探测时相对h(k)的偏移
线性探测
p(i) = i ;
探测值也可以改变
p(i) = i * c ;
Random Probing 随机探测
p(k,i) = random();
但是并不存在真随机,如果存在真随机,会出现slot无法被哈希函数搜索到