【问题标题】:Moving from Linear Probing to Quadratic Probing (hash collisons)从线性探测到二次探测(散列冲突)
【发布时间】:2011-01-21 20:02:42
【问题描述】:

我当前的哈希表实现是使用线性探测,现在我想转向二次探测(然后是链接,也许还有双重哈希)。我已经阅读了一些文章、教程、维基百科等……但我仍然不知道我应该做什么。

基本上,线性探测的步长为 1,这很容易做到。在哈希表中搜索、插入或删除元素时,我需要计算一个哈希值,为此我这样做:

index = hash_function(key) % table_size;

然后,在搜索、插入或删除时,我循环遍历表,直到找到一个空闲存储桶,如下所示:

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 */);

至于二次探测,我认为我需要做的是改变“索引”步长的计算方式,但我不明白我应该怎么做。我见过各种各样的代码,它们都有些不同。

此外,我还看到了一些二次探测的实现,其中更改了哈希函数以适应这种情况(但不是全部)。是否真的需要进行这种更改,或者我可以避免修改散列函数并仍然使用二次探测吗?

编辑: 在阅读了下面 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?

【问题讨论】:

  • 请记住,二次探测仅在表大小为素数且负载因子 eternallyconfuzzled.com/tuts/datastructures/… 了解不同冲突解决策略的概述
  • @Cristoph:这种说法不太正确。如果表大小是素数,那么如果负载因子 唯一情况。例如,它对 2 的幂的表大小和任意负载因子也有效(请参阅我的答案)。
  • @Mathew:“工作”和“有效工作”是有区别的;如果负载因子太高,(二级)集群可能会再次成为问题
  • @Cristoph:当然(“任意负载因子”对我来说可能是一个糟糕的选择;无论花哨的探测如何,0.999 的负载因子都不是一个好主意)。但是 0.75 是一个合理的负载因子,即使使用线性探测(只要散列函数良好),哈希表也能很好地工作,并且确实可以有效地使用二次探测和 2 的幂表大小(探测访问所有存储桶,如线性探测,但减少了主聚类)-因此“二次探测仅在表大小为素数且负载因子小于 0.5 时才有效”的语句不正确。

标签: c hashtable hash-collision quadratic-probing


【解决方案1】:

如果您的表大小是 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...(ith 探针的偏移量为 (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
  3. 即(p * (p+1)) / 2 = (q * (q+1)) / 2 (mod n)
  4. => (p2 + p) / 2 = (q2 + q) / 2 (mod n)
  5. => p2 + p = q2 + q (mod 2n)
  6. => q2 - p2 + q - p = 0 (mod 2n)
  7. 因式分解 => (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(模 2n)的棘手情况:
    • 观察两个因子 (q - p) 和 (p + q + 1) 之间的差为 (2p + 1),这是一个奇数 - 所以一个因子必须是偶数,另一个必须奇怪。
    • (q - p) (p + q + 1) = 0 (mod 2n) => (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 中)一定是错误的。

(如果表大小不是 2 的幂,则在第 10 步会崩溃。)

【讨论】:

    【解决方案2】:

    您不必为二次探测修改散列函数。最简单的二次探测形式实际上只是将结果平方添加到计算的位置,而不是线性的 1、2、3。

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

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

    你可以选择你的常量。

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

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


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

    因此,很难回答您的“为什么”问题。这里唯一的“为什么”是你为什么需要二次探测?其他形式的探测和获取聚簇表有问题吗?还是只是家庭作业,还是自学?

    请记住,到目前为止,哈希表最常见的冲突解决技术是链接或线性探测。二次探查是一种适用于特殊情况的启发式选项,除非您非常清楚自己在做什么,否则我不建议您使用它。

    【讨论】:

    • 抱歉,数学公式对我没有帮助。 :( 你没有给我比我已经读到的更多的信息。
    • @Nazgulled:我真的不明白你有什么问题——而且你没有其他答案,也许我不是唯一一个。我认为您应该尝试详细说明您的问题并重新措辞以准确解释您的需求
    • 我看数学公式,我不明白它们,我也不知道在代码中做什么。我需要知道用文字做什么,而不是数学公式。
    • @Nazgulled:我已经添加到我的答案中。我希望您意识到,为了成功编程,您必须了解公式,或者至少有看它们的意愿
    • 这是用于自学的porpuses。我将有一个项目,我将不得不使用哈希表(可能),但为此我将使用链接,我只想学习不同的方法,以便我可以在最终报告中写下它们并争论为什么我选择一个与另一个。对于线性探测,当我访问哈希表上的每个存储桶时我停止查找,但对于二次探测,我应该何时停止查找?
    猜你喜欢
    • 2013-06-27
    • 2018-07-03
    • 2021-01-28
    • 1970-01-01
    • 2010-12-22
    • 2023-04-04
    • 2011-01-08
    • 2016-05-14
    • 2022-11-06
    相关资源
    最近更新 更多