散列冲突解决方案
如果两个数据项被散列映射到同一个槽,需要一个系统化的方法在散列表中保存第二个数据项,这个过程称为==“解决冲突”==
如果散列函数是完美的,那么就不会有散列冲突,但完美散列函数常常是不现实的,解决散列冲突成为散列方法中很重要的一部分
-
解决散列冲突的一种方法就是为冲突的数据项再找一个开放的空槽来保存—“线性探测”
- 最简单方法就是从冲突的槽开始往后扫描,直到碰到一个空槽,如果到散列表尾部还未找到,则从手部接着扫描
- 这种寻找空槽的技术被称为"开放定址"(open addressing)
- 向后逐个槽寻找的方法则是开放定址技术中的"线性探测"(linear probing)
我们把44,55,20逐个插入到散列表中, h(44)=0,但发现0#槽已被77占据,向后找到第一个空槽1#,保存,h(55)=0,同样0#已经被占据,向后找到一个空槽2#,保存
h(20)==9,发现9#槽已经被31占据了,向后,再从头开始找到3#槽保存
- 采用线性探测方法来解决散列冲突的话,则散列表的查找也遵循同样的规则,如果在散列位置没有找到查找项的话,就必须向后做顺序查找,直到找到查找项,或者碰到空槽(查找失败)
- 线性探测法的一个缺点就是聚集的趋势,即如果同一个槽冲突的数据项较多的话,这些数据项机会在槽附近聚集起来,从而连锁式影响其它数据项的插入, 避免聚集的一个方法就是将线性探测扩展,从逐个探测改为跳跃式探测
- 重新寻找空槽的过程可以用一个更为通用的==“再散列”==来概括
- newhashvalue = rehash(oldhashvalue)
- 对于线性探测来说,rehash(pos) = (pos + 1) % sizeoftable
- "+3"的跳跃式探测则是: rehash(pos) = (pos+3) % sizeoftable
- 跳跃式探测的再散列通式是:rehash(pos) = (pos+skip) % sizeoftable
- 跳跃式探测中,需要注意的是skip的取值,不能被散列表大小整除,否则会产生周期,造成很多空槽都永远无法被探测到,一个技巧是把散列表的大小设为素数,如例子的11
- 还可以吧线性探测变为"二次探测",不再固定skip的值,而是逐步增加skip值,如1,3,5,7,9. 这样槽号就会是原散列值以平方数增加: h, h+1, h+4, h+9, h+16
-
数据项链
除了寻找空槽的开放定址技术之外,另一种解决散列冲突的方案是将容纳单个数据项的槽扩展为容纳数据项集合(或者对数据项链表的引用),
这样,散列表中的每个槽就可以容纳多个数据项,如果有散列冲突发生,只需要简单地将数据项添加到数据项集合中
查找数据项则需要查找同一个槽中的整个集合,当然,随着散列冲突的增加,对数据项的查找时间也会相应增加