哈希表:为啥在开放寻址方案中难以删除

Posted

技术标签:

【中文标题】哈希表:为啥在开放寻址方案中难以删除【英文标题】:Hash Table: Why deletion is difficult in open addressing scheme哈希表:为什么在开放寻址方案中难以删除 【发布时间】:2012-02-25 23:39:24 【问题描述】:

我正在尝试了解开放式寻址方法。我参考了 T. H. Cormen 关于这个主题的书,其中指出在开放寻址中删除是困难的。我完全被这一段困住了:

从开放地址哈希表中删除是困难的。当我们从插槽i 中删除一个键时,我们不能简单地通过在其中存储NIL 来将该插槽标记为空。这样做可能会导致无法检索任何密钥 k,在其插入期间,我们已探测插槽 i 并发现它已被占用。

我不明白这一点。请举例说明。

【问题讨论】:

【参考方案1】:

假设hash(x) = hash(y) = hash(z) = i。并假设首先插入x,然后是y,然后是z。 在开放寻址中:table[i] = xtable[i+1] = ytable[i+2] = z

现在,假设您要删除 x,并将其设置回 NULL

稍后你搜索z时,你会发现hash(z) = itable[i] = NULL,你会返回一个错误的答案:z不在表格中。

要克服这个问题,您需要使用特殊标记设置table[i],以指示搜索功能继续查看索引i+1,因为那里可能存在其哈希也是i 的元素。

【讨论】:

Amit:非常感谢您的精彩解释 Amit:你能解释一下吗:三种技术通常用于计算开放寻址所需的探测序列:线性探测、二次探测和双散列。这些技术都保证 h(k, 1), h(k, 2), ., h(k, m) 是 0, 1, . 的排列。 . . , m - 1 对于每个键 k。然而,这些技术都没有满足统一散列的假设,因为它们都不能生成超过 m2 个不同的探测序列(而不是统一散列所需的 m!)。 @Amit 如果假设 hash(x) = hash(y) = hash(z) = i ,那么如何用开放寻址来表示。在这种情况下,您需要维护记录的链接列表(或其他类型的链接)吗? @amit 我还有一个 related question here 。你能试着回答一下吗? @Geek:在“开放寻址”中,如果 hash(x) = hash(y) = i,并假设 x 首先插入到索引 i。然后,当您插入 y - 您将其插入到不同的主菜中。最简单(虽然不是最好)的方法是:insert(y,position)=if (position is occupied) insert(y,position+1); else table[position]=y【参考方案2】:

在开放式寻址方案中,查找调用一系列探测,直到找到键或找到空槽。

如果一个键涉及多个探针的链,如果沿链的某处移除其他键之一,则它将丢失(找不到),留下一个需要垫脚石的空槽。

通常的解决方案是通过将其插槽标记为可重用但实际上不为空来删除密钥。换句话说,添加了一个替代垫脚石,这样就不会缩短到其他键的探测链。

希望这有助于您的理解。

【讨论】:

提及已删除的插槽仍可重复使用的奖励积分。【参考方案3】:

从线性探测的开放寻址哈希表中删除很简单。多年来,***哈希表页面上都有它的伪代码。我不知道为什么不再存在,但这里有一个永久链接,可以追溯到它以前的时间:Old Wikipedia Hash Table page,为了您的方便,这里是伪代码:

function remove(key)
 i := find_slot(key)
 if slot[i] is unoccupied
     return   // key is not in the table
 j := i
 loop
     j := (j+1) modulo num_slots
     if slot[j] is unoccupied
         exit loop
     k := hash(slot[j].key) modulo num_slots
     if (j > i and (k <= i or k > j)) or
        (j < i and (k <= i and k > j)) (note 2)
         slot[i] := slot[j]
         i := j
 mark slot[i] as unoccupied

该页面上还有一些real code 的参考。我相信这与插入具有完全相同的性能特征。

这种删除方法比常用的“标记已删除并偶尔重新散列所有内容”要好,因为上述方法是常数时间而不是摊销常数时间。如果您有一个包含一百万个要添加和删除的项目的哈希表,在“标记已删除”方法中,偶尔的添加或删除将比之前和之后的时间长一百万倍 - 这不是良好的性能特点。

【讨论】:

如果我错了,请纠正我,但是如果只获取 key 的哈希然后比较 key 和 slot[j] 的哈希是否相等,这不是更简单吗?我要做的就是找到最后一个占用的插槽(从 j = i + 1 开始),其哈希值与 key 相同,然后将其移动到第 i 个插槽中。您有一个移动操作,最多计算 num_collisions 次的哈希 - 复杂性与 contains(key) 和 insert(key) 操作相同。 当所有插槽都被使用或标记时,搜索一个不存在的元素将永远持续下去,或者至少在最终得出它不存在之前遍历整个表。如果程序运行的时间足够长,则必须发生这种情况。 我不明白为什么它会永远持续下去。您首先找到下一个空槽,然后从那里向后搜索可能散列为 i 的条目。如果找到(插槽 j),则将其放入第 i 个插槽。然后对插槽 j 重复此过程,直到找不到匹配项。当然,最坏的情况是你扫描所有东西,但假设负载因子足够低,这应该在 O(1) 时间内执行。

以上是关于哈希表:为啥在开放寻址方案中难以删除的主要内容,如果未能解决你的问题,请参考以下文章

链式与开放式寻址的哈希表中的缓存性能

哈希表-开放寻址法-拉链法

哈希表(开放寻址法)

哈希表(开放寻址法)

哈希表与树的介绍

数组链表与哈希