关闭

Hash学习(3)-冲突的解决

675人阅读 评论(0) 收藏 举报

     为提高hash表查找性能,除了考虑选择合适的hash表表长和完美的hash函数外,还必须考虑hash表处理冲突的能力。当hash函数对两个不同的数据项产生了相同的hash值时,冲突就产生了。对于冲突的处理,通常采用的方法可以分为三类:

(1)线性再散列法,简单的按顺序遍历hash表,寻找下一个可用的槽;

(2)非线性再散列法,计算一个新的hash值;

(3)外部拉链法,将hash表中的每个槽当作具有相同hash值的数据项所组成链表的头部,hash表将发生冲突的项添加到同一个链表中。

下面对这三种方法分别介绍。

1.线性再散列法

       线性再散列法是形式最简单的处理冲突的方法。插入元素时,如果发生冲突,算法会简单的遍历hash表,直到找到表中的下一个空槽,并将该元素放入该槽中。查找元素时,首先散列值所指向的槽,如果没有找到匹配,则继续遍历hash表,直到:(1)找到相应的元素;(2)找到一个空槽(指示查找的元素不存在);(3)整个hash表遍历完毕(指示该元素不存在并且hash表是满的)。下表显示了以线性再散列法将{89,18,49,58,69}5个元素插入hash表的过程。(hash函数为:hash(X)=X mod 10;hash表长一般用素数,这里为了说明方便取表长为10)


         第一次冲突发生在插入关键字49时,它被放在下一个空闲地址,即地址0。关键字58依次和18,89,49发生冲突,试选三次之后才找到一个空单元。对69的冲突用类似的方法处理。从以上过程可以看出,只要表中有空闲单元,总可以找到,但这里选择步长为1,将会在hash表中产生聚集,即:即使hash表相对较空,还是会在某些区域形成一些区块,这些区块中的任何活动都将设计更大的步长。但如果以5或更大的值作为步长,可以迅速地从拥挤区域移开,从而减少聚集现象的发生。事实上,只要hash表长和检查槽的步长是互质的,那么表中的每个槽都会被检查到。

       线性再散列法有两个缺点:第一,不能从表中删除元素,因为相应的单元可能已经引起过冲突,元素绕过它存到了别处,例如,如果我们删除了18,那么其他的元素都会找不到。如果确实需要删除,可以采用懒惰删除的方法。第二,当表被填满时性能下降明显。

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

2.非线性再散列法

        线性再散列法是从冲突位置开始,采用一个步长以顺序方式遍历hash表,来查找一个可用的槽,从上面的讨论可以看出,它容易产生聚集现象。非线性再散列法可以避免遍历散列表,它会计算一个新的hash值,并通过它跳转到表中一个完全不同的部分。它的思想就是:通过跳转到表中不同的部分,从而避免相似值的聚集,如果再散列函数跳转到的槽已经被占用了,则继续执行新一轮的再散列和跳转。

    例如,还是上面的例子,如果再散列函数是hash(X)=R-(X mod R),其中R为小于hash表长的素数,如果我们选择R=7,则下表显示了插入与前面相同的关键字的结果。


        第一个冲突发生在49被插入的时候, hash(49)=7-0=7,故49被插入到位置6。Hash(58)=7-2=5,于是58被插入到位置3。最后69产生冲突,从而被插入到距离为hash(69)=7-6=1的地方。

        非线性再散列法也有不能从表中删除元素的缺点。

        无论是使用线性再散列法还是非线性再散列法,只有在散列表不会接近填满的情况下,才能使用再散列。当散列表的负载因子增大时,再散列所花费的时间也会显著增加。通过以上讨论可以看出,再散列方法适用于表负载较低并且不太可能执行删除操作的情况。

3.外部拉链法

        外部拉链法是将hash表看作是一个链表数组,表中的每个槽要不为空,要不指向hash到该槽的表项的链表。可以通过把元素添加到链表中来解决冲突。同样,可以通过从链表中删除元素来执行删除操作。因此,解决冲突的代价不会超过向链表中添加一个节点,不需要执行再散列。在再散列中,表项的最大数量是由表中槽的原始数量确定的,与之不同的是,外部拉链法可以容纳的元素于将在内存中存放的元素一样多。

        外部拉链法的原则是:hash表的大小一般与预料的元素个数差不多。

        假设有一个表长为10的hash表,给出10个关键字为前10个自然数的平方,hash函数为hash(X)=X mod 10,下图就是对应的外部拉链法的hash表。


          外部拉链法的平均查找时间是对链表的查找时间加上1,这个1是最初的定位hash表槽。外部拉链法的缺点是:它需要稍微多一些的空间来实现,因为添加任何元素都需要添加指向节点的指针,并且每次探查也要花费稍微多一点的时间,因为它需要间接引用指针,而不是直接访问元素。由于今天的内存成本很低并且可以使用非常快的CPU,所以这些缺点都是微不足道的。因此,实际使用hash表时,一般都是使用拉链法来解决hash冲突。

与开放定址法相比,拉链法有如下几个优点:
①拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
②由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
③开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
④在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表,删除结点不能简单地将被删结 点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在 用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。

(3)拉链法的缺点
     拉链法的缺点是:指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。


转自 http://blog.csdn.net/qll125596718/article/details/7028322

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:1722375次
    • 积分:13173
    • 等级:
    • 排名:第1032名
    • 原创:171篇
    • 转载:203篇
    • 译文:0篇
    • 评论:92条
    最新评论