为什么随机探测在哈希表实现中不受欢迎?

时间:2009-11-10 18:14:49

标签: language-agnostic data-structures hashtable random hash-collision

根据各种来源,例如维基百科和Google发现的各种.edu网站,哈希表解决冲突的最常见方式是线性或二次探测和链接。简要提及随机探测,但没有给予太多关注。我已经实现了一个使用随机探测来解决冲突的哈希表。假设存在冲突,解决方案的工作原理如下:

  1. 对象的完整(32位)哈希用于为线性同余随机数生成器设定种子。
  2. 生成器生成32位数字,并采用模数确定哈希表中接下来要探测的位置。
  3. 这具有非常好的属性,无论模数空间中有多少哈希冲突,只要在完整的32位哈希空间中几乎没有冲突,查找和插入时间就预期为O(1)。因为探测序列是伪随机的,所以与线性探测不同,没有聚类行为来自模数空间碰撞。由于整个系统是开放式地址,并且不在任何地方使用链接列表,因此与链接不同,您不需要在每次插入时执行内存分配。

    此外,由于散列的大小通常是地址空间的大小(32位机器上的32位),因此根本无法在地址空间中放入足够的项目来导致大量的散列冲突。良好的散列方案下的位哈希空间。

    为什么随机探测这种不受欢迎的碰撞解决策略?

5 个答案:

答案 0 :(得分:7)

使用线性查找(例如double hasing)的原因之一是缓存局部性。 通过使第二个(rehash)函数成为一个小整数的加法,大多数机会是你会遇到相同的缓存行。这对于大型哈希来说非常重要。

由于其简单性,可能会使用链式散列。

答案 1 :(得分:4)

可能的原因是线性或二次探测

  • 具有相同的最坏情况时间复杂度(O(表的大小))
  • 具有相同的最佳案例时间复杂度(O(1))
  • 更容易实施
  • 比一个好的RNG更快(因为速度是哈希表的一个主要卖点)

但我不确定。您是否使用其他冲突解决方案实现自己的哈希表并在不同情况下比较两者?这将是非常有启发性的。

答案 2 :(得分:4)

Python的字典实现就是这样做的。 dictobject.c中的一条非常好的评论说:

...
The first half of collision resolution is to visit table indices via this
recurrence:

    j = ((5*j) + 1) mod 2**i

For any initial j in range(2**i), repeating that 2**i times generates each
int in range(2**i) exactly once (see any text on random-number generation for
proof).
...

对我来说,看起来像是一个线性同余RNG!

请注意,这样一个RNG的完整状态只是 i 位 - 必须是,以避免重新访问条目 - 所以你不能有意义地使用“[t]他满(32) -bit)对象的哈希“以种子RNG。 Python最初使用哈希中的 i 位来种子 j 。如果存在另一个冲突,它会从散列中抓取另外5位并将其抛入混合中。 (阅读其余的评论,特别是在它讨论PERTURB_SHIFT的地方。)它继续这样,每次碰撞添加更多位,直到它用尽了整个哈希码。这样,Python就可以使用散列码提供的大量随机性,代码简单快速。

这是我读过的最好的代码。它在Beautiful Code的第18章中有所体现。所以我会说你有所作为!

答案 3 :(得分:0)

难道你不会遇到插入非稀疏填充表的问题,不能保证在开始迭代重复元素之前你会点击哈希表的所有元素吗?

因此,插入时间不会很明确。

答案 4 :(得分:0)

我认为随机哈希的原因并没有太多使用,当从32位哈希计算小哈希值时,哈希冲突很容易变得罕见,除非哈希函数有“错误”,并且在那里在这种情况下,散列函数的所有32位都匹配的可能性很大(例如,因为只有部分密钥用于计算散列)。如果哈希函数是合适的,并且负载因子相当低,则线性和二次探测提供了良好的缓存局部性(请记住,大多数哈希冲突将通过仅查看一个额外项来解决,这将使用线性和二次探针来进行一个跟随第一个猜测的)。线性探针在所有键映射到相同值的情况下提供稍好的性能,有时即使它们映射到少量值。链斗式散列可以轻松移除物品。