什么是Hash冲突

由于Hash原理是将输入空间的值映射到Hash空间内,但Hash值的空间远远小于输入的空间。根据鸽巢原理,一定会存在不同输入被映射成相同输出的过程,这种情况称为“散列碰撞(collision)”。在密码学中,散列函数必须具有不可逆性。

作为一个好的Hash算法,我们需要这种冲突的概率尽可能小。

鸽巢原理,又名狄利克雷抽屉原理、鸽笼原理。

其中一种简单的表述法为:

若有n个笼子和n+1只鸽子,所有的鸽子都被关在鸽笼里,那么至少有一个笼子有至少2只鸽子。 另一种为:

若有n个笼子和kn+1只鸽子,所有的鸽子都被关在鸽笼里,那么至少有一个笼子有至少k+1只鸽子。 集合论的表述如下:

若A是n+1元集,B是n元集,则不存在从A到B的单射。

拉姆齐定理是此原理的推广。

处理冲突

为了知道冲突产生的相同散列函数地址所对应的关键字,必须选用另外的散列函数,或者对冲突结果进行处理。而不发生冲突的可能性是非常之小的,所以通常对冲突进行处理。常用方法有以下几种:

开放地址法 open addressing

简单来说就是:
一旦发生冲突,就去寻找下 一个空的散列表地址,只要散列表足够大,空的散列地址总能找到。

Hash冲突的解决:开放地址法和链地址法
Hash冲突的解决:开放地址法和链地址法
其中,
Hash冲突的解决:开放地址法和链地址法
为散列函数,
Hash冲突的解决:开放地址法和链地址法
为散列表长,
Hash冲突的解决:开放地址法和链地址法
为增量序列,Hash冲突的解决:开放地址法和链地址法
为已发生冲突的次数。
增量序列可有下列取法:

线性探测法 ThreadLocalMap

Hash冲突的解决:开放地址法和链地址法
称为 线性探测(Linear Probing)

线性再散列法是形式最简单的处理冲突的方法。

插入元素时,如果发生冲突,算法会简单的从该槽位置向后循环遍历hash表,直到找到表中的下一个空槽,并将该元素放入该槽中(会导致相同hash值的元素挨在一起和其他hash值对应的槽被占用)。
查找元素时,首先散列值所指向的槽,如果没有找到匹配,则继续从该槽遍历hash表,直到:

  1. 找到相应的元素
  2. 找到一个空槽,指示查找的元素不存在
  3. 整个hash表遍历完毕(指示该元素不存在并且hash表是满的)

显示线性探测填装一个散列表的过程:
关键字为{89,18,49,58,69}插入到一个散列表中的情况。此时线性探测的方法是取di=i。并假定取关键字除以10的余数为散列函数法则。

Hash冲突的解决:开放地址法和链地址法
第一次冲突发生在填装49的时候。地址为9的单元已经填装了89这个关键字,所以取i=1,往下查找一个单位,发现为空,所以将49填装在地址为0的空单元。
第二次冲突则发生在58上,取i=3,往下查找3个单位,将58填装在地址为1的空单元。69同理。
表的大小选取至关重要,此处选取10作为大小,发生冲突的几率就比选择质数11作为大小的可能性大。越是质数,mod取余就越可能均匀分布在表的各处。

用线性探测法处理冲突,思路清晰,算法简单,但存在下列缺点:

  1. 处理溢出需另编程序。一般可另外设立一个溢出表,专门用来存放上述哈希表中放不下的记录。此溢出表最简单的结构是顺序表,查找方法可用顺序查找。
  2. 按上述算法建立起来的哈希表,删除工作非常困难。如果将此元素删除,查找的时会发现空槽,则会认为要找的元素不存在。只能标上已被删除的标记,否则,将会影响以后的查找。
  3. 线性探测法很容易产生堆聚现象。所谓堆聚现象,就是存入哈希表的记录在表中连成一片。按照线性探测法处理冲突,如果生成哈希地址的连续序列愈长 ( 即不同关键字值的哈希地址相邻在一起愈长 ) ,则当新的记录加入该表时,与这个序列发生冲突的可能性愈大。因此,哈希地址的较长连续序列比较短连续序列生长得快,这就意味着,一旦出现堆聚 ( 伴随着冲突 ) ,就将引起进一步的堆聚。

线性补偿探测法

线性补偿探测法的基本思想是:将线性探测的步长从 1 改为 Q ,即将上述算法中的
hash = (hash + 1) % m 改为:hash = (hash + Q) % m = hash % m + Q % m,而且要求 Q 与 m 是互质的,以便能探测到哈希表中的所有单元。

伪随机探测

伪随机探测的基本思想是:将线性探测的步长从常数改为随机数,
令: hash = (hash + RN) % m ,其中 RN 是一个随机数。
在实际程序中应预先用随机数发生器产生一个随机序列,将此序列作为依次探测的步长。这样就能使不同的关键字具有不同的探测次序,从而可以避 免或减少堆聚。基于与线性探测法相同的理由,在线性补偿探测法和随机探测法中,删除一个记录后也要打上删除标记。

链地址法

链表地址法是使用一个链表数组,来存储相应数据,当hash遇到冲突的时候依次添加到链表的后面进行处理。

Hash冲突的解决:开放地址法和链地址法
链地址在处理的流程如下:添加一个元素的时候,首先计算元素key的hash值,确定插入数组中的位置。如果当前位置下没有重复数据,则直接添加到当前位置。当遇到冲突的时候,添加到同一个hash值的元素后面,行成一个链表。这个链表的特点是同一个链表上的Hash值相同。

假设散列长为8,散列函数H(K)=K mod 7,给定的关键字序列为{32,14,23,2, 20}当使用链表法时,相应的数据结构如下图所示:
Hash冲突的解决:开放地址法和链地址法

相关文章: