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