什么是Hash冲突
在Hash表中,Hash函数进行计算时,对应不同的关键字可能获得相同的Hash地址,即 key1≠key2,但是f(key1)=f(key2)。这种现象就是冲突,而且这种冲突只能尽可能的减少,不能完全避免。
为了解决这种冲突,我们常用四种方法来解决:
- 开放地址法
- 再哈希法
- 链地址法
- 建立公共溢出区法
开放地址法
所谓的开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
开放地址法公式为:fi(key) = (f(key)+di) MOD m (di=1,2,3,……,m-1)
用开放定址法解决冲突的做法是:当冲突发生时,使用某种探测技术在散列表中形成一个探测序列。沿此序列逐个单元地查找,直到找到给定的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存入该地址单元)。查找时探测到开放的地址则表明表中无待查的关键字,即查找失败。
假设,我们的关键字集合为{12,67,56,16,25,37,22,29,15,47,48,34},表长为12。我们用散列函数 f(key) = key mod 12 。当计算前5个数{12,67,56,16,25}时,都是没有冲突的散列地址,直接存入:
计算key = 37时,发现f(37) = 1,此时就与25所在的位置冲突。
于是我们应用上面的公式f(37) = (f(37)+1) mod 12 = 2。于是将37存入下标为2的位置:
开放地址法容易产生堆积问题,不适于大规模的数据存储。在插入时可能会出现多次冲突的现象,删除的元素是多个冲突元素中的一个,需要对后面的元素作处理,实现较为复杂。同时结点规模很大时会浪费很多空间。
再哈希法
简单来说再哈希法就是再使用哈希函数去散列一个输入的时候,输出是同一个位置就再次哈希,直至不发生冲突位置为止。
比如第一次按照姓首字母进行哈希,如果产生冲突可以按照姓字母首字母第二位进行哈希,再冲突,第三位,直到不冲突为止。
再哈希法虽然不易发生聚集,但是每次冲突都要重新哈希,计算时间增加。
链地址法
链地址法的基本思想是:每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表连接起来。即每一个哈希表节点都是一个链表的头结点。
HashMap,HashSet其实都是采用的拉链法来解决哈希冲突的,就是在每个位桶实现的时候,我们采用链表(jdk1.8之后采用链表+红黑树)的数据结构来去存取发生哈希冲突的输入域的关键字。
例如,键值对k2, v2与键值对k1, v1通过计算后的索引值都为2,这时即产生冲突,但是可以通过next指针将k2, k1所在的节点连接起来,这样就解决了哈希的冲突问题。
拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短。由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况。并且在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。但是链表中的指针需要额外的空间,故当结点规模较小时,开放定址法相对链地址法来说较为节省空间。
建立公共溢出区法
这种方法的基本思想是:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。简单来说就是将冲突的元素都放在另一个地方,不在表里出现。
参考文献
https://www.cnblogs.com/lyfstorm/p/11044468.html