- 哈希表
- 哈希函数选择
- 哈希碰撞
由“符号表问题”引入什么是哈希
有一个表S有n条记录,每个记录(通常认为是指向数据的指针x)有一个Key和一些数据(属于键值key的附加数据),我们需要对这个表进行一系列操作:Insert、Search、Delete
最简单实现:直接映射表(键值分布比较小时有用)
假设键值来自一个有m个元素的集合U,并假设键值互相独立,建立一个数组T[0,1…m-1],对于关键字k,如果在集合U中,把它存储在T[k]的位置上,查询、插入、删除的时间都是O(1)
这个方法的缺陷
如果关键字的范围比较大,那么建立的这个表的长度那么就大到无法接受
如果关键字不连续非常浪费空间
Hashing
实现字典的一种数据结构就是哈希表,又称散列表,根据一个哈希函数将集合S中的关键字映射到一个表中,这个表就称为哈希表,就是把上述的直接映射,变成一个函数映射,这个函数就是哈希函数,而这种方法就称为Hashing
映射过程会遇到的问题:碰撞
多个键值经过哈希后指向同一个槽,碰撞有多种解决办法
链接法
把相同键值的数据存储在链表中
最坏情况,所有的键都落到一个槽中,操作的复杂度等于在链表中操作复杂度θ(n)
最好情况,O(1)
给定一个能存放n个元素,具有m个槽的散列表T,定义T的装载因子α为n/m,假设计算key的时间为O(1),查找关键字为k的元素的时间取决于T[h(k)]的链表长度,平均情况下一次成功的查找需要θ(1+α)的时间
如何选择哈希函数h
一个好的哈希函数应该能够将关键字均匀的分配到哈希表T中
关键字的分布率不应该影响这种均匀性
最常用的哈希函数
除法散列法
定义hash函数为 h(k) = k mod m
m的原则就是m选为质数且不能太接近2或者10的幂次
因为键值的低位可能具有某种分布规律,如果选择2或者10的幂次容易出现冲突
极端的一些例子
m=2
表中的所有的奇数位置都没用到,这便是关键字的分布规律(全偶)影响到了映射的均匀性
m=2r
我们把关键字用二进制表示,那么如下的一个关键字,
乘法散列法
设m=2r, 计算机是w-bit长的字,定义哈希函数是h(k) = (A*k mod 2^w) rsh (w-r)
rsh->右移位,A是一个在(2^w-1, 2^w)范围内的奇数
分析这个哈希函数(A*k mod 2^w)这一部分就是将乘法得到的结果只取一个字长 ,然后再rsh w-r位,就刚好只保留了最大是m的结果,可以很好的映射到表中。
假设m=23,字长w是7-bit ,用车轮法考虑那个乘法过程:
其他解决碰撞的方法
开放寻址法
解决思路:当遇到冲突时那么便去寻找下一个空的地址,只要表足够大(表长度要大于集合S的数量),空的地址总能找到
线性探查
H(k, i) = (H(k) + i) mod m (i = 1,2,3,4....)
假设m=13,i=1,H(18) = 5 发生冲突,寻找下一个地址
H(18) = (5 + 1) mod 13 = 6 继续发生冲突,寻找下一个地址
...
这种算法可能遇到一次集群(二次集群,取决与每次加的i,问题是相同的)的问题,如果某个连续空间都被占用,浪费效率
二次哈希
H(k, i) = (H_1(k) + i·H_2(k)) mod m (i = 1,2,3,4)
对于一个开放寻址的哈希表,α=n/m<1,那么一次不成功搜索的预期探寻次数为1/(1-α).