从线性探测转向二次探测(哈希碰撞)

时间:2010-02-27 17:04:44

标签: c hashtable hash-collision quadratic-probing

我目前使用线性探测器的实现正在使用线性探测,现在我想转向二次探测(以后再进行链接,也可能是双重哈希)。我已经阅读了一些文章,教程,维基百科等...但我仍然不知道我应该做什么。

线性探测基本上是1步,这很容易做到。当从哈希表中搜索,插入或删除元素时,我需要计算哈希值,为此我执行此操作:

index = hash_function(key) % table_size;

然后,在搜索,插入或删除I循环通过表时,直到找到一个空闲桶,如下所示:

do {
    if(/* CHECK IF IT'S THE ELEMENT WE WANT */) {
        // FOUND ELEMENT

        return;
    } else {
        index = (index + 1) % table_size;
    }
while(/* LOOP UNTIL IT'S NECESSARY */);

对于Quadratic Probing,我认为我需要做的是改变计算“索引”步长的方式,但这是我不明白应该怎么做的。我见过各种代码,而且所有代码都有所不同。

另外,我已经看到了一些Quadratic Probing的实现,其中哈希函数被改变为适应(但不是全部)。是真的需要改变还是我可以避免修改散列函数并仍然使用Quadratic Probing?

修改 在阅读了以下Eli Bendersky指出的所有内容后,我想我得到了一般的想法。以下是http://eternallyconfuzzled.com/tuts/datastructures/jsw_tut_hashtable.aspx代码的一部分:

15   for ( step = 1; table->table[h] != EMPTY; step++ ) {
16     if ( compare ( key, table->table[h] ) == 0 )
17       return 1;
18 
19     /* Move forward by quadratically, wrap if necessary */
20     h = ( h + ( step * step - step ) / 2 ) % table->size;
21   }

有两件事我没有得到......他们说二次探测通常是使用c(i)=i^2完成的。但是,在上面的代码中,它正在执行更像c(i)=(i^2-i)/2

的操作

我准备在我的代码上实现这个,但我只是这样做:

index = (index + (index^index)) % table_size;

......而不是:

index = (index + (index^index - index)/2) % table_size;

如果有的话,我会这样做:

index = (index + (index^index)/2) % table_size;

...因为我看过其他代码示例潜水两个。虽然我不明白为什么......

1)为什么要减去步骤?
2)为什么要潜水2?

2 个答案:

答案 0 :(得分:11)

如果您的表格大小是2的幂,则有一种特别简单而优雅的方法来实现二次探测:

step = 1;

do {
    if(/* CHECK IF IT'S THE ELEMENT WE WANT */) {
        // FOUND ELEMENT

        return;
    } else {
        index = (index + step) % table_size;
        step++;
    }
} while(/* LOOP UNTIL IT'S NECESSARY */);

不是从原始索引中查看偏移量0,1,2,3,4 ......而是查看偏移量0,1,3,6,10 ......(i th < / sup> probe位于偏移量(i *(i + 1))/ 2,即它是二次方。)

这可以保证打到哈希表中的每个位置(所以如果有的话,你可以保证找到一个空桶)提供表的大小是2的幂。


以下是证明的草图:

  1. 如果表格大小为n,我们希望显示我们将获得n个不同的值(i *(i + 1))/ 2(mod n),其中i = 0 ... n-1。
  2. 我们可以通过矛盾证明这一点。假设存在少于n个不同的值:如果是,则在[0,n-1]范围内对于i必须至少有两个不同的整数值,使得(i *(i + 1))/ 2(mod n) )是一样的。将这些p和q称为p,其中p < Q值。
  3. 即。 (p *(p + 1))/ 2 =(q *(q + 1))/ 2(mod n)
  4. =&GT; (p 2 + p)/ 2 =(q 2 + q)/ 2(mod n)
  5. =&GT; p 2 + p = q 2 + q(mod 2n)
  6. =&GT; q 2 - p 2 + q - p = 0(mod 2n)
  7. Factorise =&gt; (q - p)(p + q + 1)= 0(mod 2n)
  8. (q - p)= 0是平凡的情况p = q。
  9. (p + q + 1)= 0(mod 2n)是不可能的:我们的p和q的值在[0,n-1]和q>的范围内。 p,所以(p + q + 1)必须在[2,2n-2]范围内。
  10. 当我们使用模2n时,我们还必须处理两个因子都不为零但是乘以0(mod 2n)的棘手情况:
    • 观察到两个因子(q - p)和(p + q + 1)之间的差异是(2p + 1),这是一个奇数 - 因此其中一个因子必须是偶数,而另一个必须是是奇怪的。
    • (q-p)(p + q + 1)= 0(mod 2n)=&gt; (q - p)(p + q + 1)可被2n整除。 如果n(因而2n)是2 的幂,则需要偶数因子为2n的倍数(因为2n的所有素因子都是2,而没有任何素因子我们奇怪的因素是)。
    • 但是(q - p)的最大值为n-1,而(p + q + 1)的最大值为2n-2(如步骤9所示),因此两者都不能是2n的倍数
    • 所以这种情况也是不可能的。
  11. 因此,假设少于n个不同的值(在步骤2中)必须为假。
  12. (如果表格大小 2的幂,则在步骤10中分崩离析。)

答案 1 :(得分:4)

您不必修改二次探测的哈希函数。最简单的二次探测形式实际上只是将计算出的位置添加到计算位置而不是线性1,2,3。

有一个很好的资源here。以下是从那里开始的。当使用简单多项式c(i) = i^2时,这是最简单的二次探测形式:

alt text

在更一般的情况下,公式为:

alt text

你可以选择你的常数。

但请记住,二次探测仅在某些情况下有用。正如Wikipedia entry所述:

  

二次探测提供了良好的记忆力   缓存,因为它保留了一些   参考地点;但是,线性的   探测具有更大的局部性,   因此,更好的缓存性能。   二次探测更好地避免了   可能发生的聚类问题   线性探测,虽然不是   免疫


编辑:与计算机科学中的许多事物一样,二次探测的精确常数和多项式是启发式的。是的,最简单的形式是i^2,但您可以选择任何其他多项式。维基百科给出了h(k,i) = (h(k) + i + i^2)(mod m)的示例。

因此,很难回答你的“为什么”的问题。这里唯一的“为什么”是为什么你需要二次探测?遇到其他形式的探测和获取群集表的问题?或者只是作业或自学?

请记住,到目前为止,哈希表最常见的冲突解决方法是链接或线性探测。二次探测是一种适用于特殊情况的启发式选项,除非您知道自己做得很好,否则我不建议使用它。