哈希函数很强大,最近算法课老师讲了哈希函数的一系列应用。这里总结一下。
一、哈希表
哈希表(Hash Table)也叫散列表,是根据关键码值(Key Value)而直接进行访问的数据结构。它通过把关键码值映射到哈希表中的一个位置来访问记录,以加快查找的速度。这个映射函数就做散列函数,存放记录的数组叫做散列表。以数据中每个元素的关键字K为自变量,通过散列函数H(k)计算出函数值,以该函数值作为一块连续存储空间的的单元地址,将该元素存储到函数值对应的单元中。 哈希表存储的是键值对,其查找的时间复杂度与元素数量多少无关,哈希表在查找元素时是通过计算哈希码值来定位元素的位置从而直接访问元素的,因此,哈希表查找的时间复杂度为O(1),最坏的情况下查找一个元素的时间与在链表中查找一个元素的时间相同。最坏的情况下都是O(N)。有一个常用的概念定义为load factor(装载因子)算法导论中给的定义为:给定一个能存放n个元素的,具有m个槽位的散列表T。定义T的装载装载因子a为n/m,即一个链中平均存储的元素。一个好的散列函数应满足简单一致散列的假设。每个关键字都等可能的散列到m个槽位的任何一个之中去,并与其它的关键字已被散列到哪一个槽位中无关。
二、散列函数
1.除法散列法
在用来设计散列函数的除法散列法之中,通过取k除以m的余数,来将关键字k映射到m个槽中去,定义散列函数为
h(k)=k mod m
例如,如果散列表的大小为12,所给关键字k=100,h(k)=4。这种方法只要一次除法操作,所以比较快。
当应用除法散列时,要注意m的选择。例如,m不应该是2的幂。因为如果m为2的幂。则h(k)就是k的p个最低位数字。除非我们事先知道,关键字的概率分布使得k的各种最低p位的排列形式的可能性相同。否则在设计散列函数时,最好考虑关键字所有位的情况。我们通常选择m的值常常是与2的整数幂不太接近的质数。
2.乘法散列法
构造散列函数的乘法方法包含两个步骤。第一步,用关键字k乘上常数A(0<A<1),并抽取kA的小数部分。然后用m乘以这个值,再取结果的底(floor)总之,散列函数为其中‘kA mod 1’即 kA 的小数部分。
h(A)=m(kA mod 1)
乘法方法的一个优点是对m的选择没有什么特别的要求。一般我们选择A约为0.618(黄金分割点).
3.全域散列
如果让某个与你作对的人来选择要散列的关键字,那么他会选择全部散列到同一个槽中的n个关键字。唯一有效地改进方法是随机地选择散列函数,是指独立于要存储的关键字,这种方法称之为全域散列。其基本思想是在执行开始时,就从一组仔细设计的函数中,随机地选择一个座位散列函数。随机化保证了没有哪一种输入会始终导致最坏情况的放生。使得即使对同一个输入,算法在每一次执行时的状态也都不一样。主要过程如下:
设H为有限的一组散列函数,它将给定的关键字域映射到{0,1,...,m-1}这样的一个函数组称为全域的。如果对每一对不同的关键字k,l满足h(k)=h(l)的散列函数的个数至多为|H|/m。换言之,如果从H中随机地选择一个散列函数,当关键字k!=l是,亮着发生碰撞的概率不大于 1/m。
三、处理冲突的方法
1.链地址法。
2.开放定址法――线性探测
线性探测法的地址增量di = 1, 2, ... , m-1,其中,i为探测次数。该方法一次探测下一个地址,知道有空的地址后插入,若整个空间都找不到空余的地址,则产生溢出。
线性探测容易产生“聚集”现象。当表中的第i、i+1、i+2的位置上已经存储某些关键字,则下一次哈希地址为i、i+1、i+2、i+3的关键字都将企图填入到i+3的位置上,这种多个哈希地址不同的关键字争夺同一个后继哈希地址的现象称为“聚集”。聚集对查找效率有很大影响。
3.开放地址法――二次探测
二次探测法的地址增量序列为 di = 12, -12, 22, -22,… , q2, -q2 (q <= m/2)。二次探测能有效避免“聚集”现象,但是不能够探测到哈希表上所有的存储单元,但是至少能够探测到一半。