六种哈希函数的构造方法:
(1)直接定址法
函数公式:f(key) = a * key + b(a,b为常数)
这种方法的优点是:简单、均匀,不会产生冲突。但是需要事先知道关键字的分布情况,适合查找表较小并且连续的情况。
(2)数字分析法
也就是取出关键字中的若干位组成哈希地址。比如我们的11位手机号是“187****1234”,其中前三位是接入号,一般对应不同的电信公司。中间四位表示归属地。最后四位才表示真正的用户号。
如果现在要存储某个部门的员工的手机号,使用手机号码作为关键字,那么很有可能前面7位都是相同的,所以我们选择后面的四位作为哈希地址就不错。
(3)平方取中法
取关键字平方后的中间几位作为哈希地址。由于一个数的平方的中间几位与这个数的每一位都有关,所以平方取中法产生冲突的机会相对较小。平方取中法所取的位数由表长决定。
如:K=456,K^2=207936,如果哈希表的长度为100,则可以取79(中间两位)作为哈希函数值。
(4)折叠法
折叠法是将关键字从左到右分割成位数相等的几个部分(最后一部分位数不够可以短),然后将这几部分叠加求和,并按哈希表表长,取后几位作为哈希地址。当关键字位数很多,而且关键字中每一位上数字分布大致均匀时,可以使用折叠法。
如:我们的关键字是9876543210,哈希表表长三位,我们可以分为四组:987 | 654 | 321 | 0,然后将他们叠加求和:987+654+321+0 = 1962,再取后三位就可以得到哈希地址为962.
(5)除留余数法
选择一个适当的正整数p(p<=表长),用关键字除以p,所得的余数可以作为哈希地址。即:H(key) = key % p(p<=表长),除留余数法的关键是选取适当的p,一般选p为小于或等于哈希表的长度(m)的某个素数。
如:m = 8,p=7.
m = 16,p = 13.
m = 32,p = 31.
(6)随机数法
函数公式:f(key) = random(key). 这里的random是随机函数,当关键字的长度不等时,采用这种方式比较合适。
总之,哈希函数的规则就是:通过某种转换关系,使关键字适度的分散到指定大小的顺序结构中。越分散,查找的时间复杂度就越小, 空间复杂度就越高。哈希查找明显是一种以空间换时间的算法。
上面提到了如何构造一个哈希函数,那就不得不提如何避免冲突的算法。
(1)开放定址法
当冲突发生时,使用某种方法在哈希表中形成一探查序列。然后沿着该探查序列逐个单位的查找,直到找到一个开放的地址(即该地址单元为空)为止。对于哈希表中形成一探查序列时,可以有3种不同的方法:
1.线性探测法
将散列看成一个环形表,探测序列是(假设表长为m):
H(k),H(k)+1,H(k)+2.....m-1,0,1......H(k)-1。用线性探测法解决冲突时,求下一个开放地址的公式为:Hi = (H(k)+i) MOD m.
2.二次探测法
二次探测法的探测序列依次是12,-12,22,-22等等。当发生冲突时,求下一个开放地址的公式为:
H2i-1 = (H(k)+i2) MOD m
H2i = (H(k)-i2) MOD m (1=< i <= (m-1)/2 )
优点:减少了堆集发生的可能性;
缺点:不容易探测到哈希表空间。
3.伪随机探测法
采用随机探测法解决冲突时,下一个开放地址的公式为:Hi = (H(k)+Ri) MOD m。
其中R1,R2,...,Rm-1是一个随机排列。
(2)再哈希法
当冲突发生时,使用另一个函数计算得到一个新的哈希地址,直到冲突不再发生时为止。Hi = RHi(key) i = 1,2,…,k 。其中RHi均是不同的哈希函数。优点是不易产生聚集,缺点是增加了计算时间。
(3)链地址法
将所有关键字为同义词的结点链接在同一个单链表中。若选定的哈希函数所产生的哈希地址为0~m-1,则可以将哈希表定义成一个由m个链表头指针组成的指针数组。优点是:不产生聚集;由于结点空间是动态申请的,故更适合造表前无法确定表长的情况;从表中删除节点容易。
(4)公共溢出区法
假设哈希函数的值域为[0...m-1],则设向量HashTable[0...m-1]为基本表,每个分量存放一个记录,另设立向量OverTable[0..v]为溢出表。所有关键字和基本表中关键字为同义词的记录,不管它们由哈希函数得到的哈希地址是什么,一旦发生冲突,都被填入溢出表中。
在哈希表上进行查找的过程和建表的过程基本一致。假设给定的值为k,根据建表时设定的哈希函数H,计算出哈希地址H(k),若表中该地址对应的空间未被占用。则查找失败。否则将该地址中的节点与给定值k比较,若相等则查找成功,否则按建表时设定的处理冲突方法找下一个地址,如此反复下去,直到找到某个地址空间未被占用(查找失败)或者关键字比较相等(查找成功)为止。