【发布时间】:2012-02-25 23:39:24
【问题描述】:
我正在尝试了解开放式寻址方法。我参考了 T. H. Cormen 关于这个主题的书,其中指出在开放寻址中删除是困难的。我完全被这一段困住了:
从开放地址哈希表中删除是困难的。当我们从插槽
i中删除一个键时,我们不能简单地通过在其中存储NIL来将该插槽标记为空。这样做可能会导致无法检索任何密钥k,在其插入期间,我们已探测插槽i并发现它已被占用。
我不明白这一点。请举例说明。
【问题讨论】:
我正在尝试了解开放式寻址方法。我参考了 T. H. Cormen 关于这个主题的书,其中指出在开放寻址中删除是困难的。我完全被这一段困住了:
从开放地址哈希表中删除是困难的。当我们从插槽
i中删除一个键时,我们不能简单地通过在其中存储NIL来将该插槽标记为空。这样做可能会导致无法检索任何密钥k,在其插入期间,我们已探测插槽i并发现它已被占用。
我不明白这一点。请举例说明。
【问题讨论】:
假设hash(x) = hash(y) = hash(z) = i。并假设首先插入x,然后是y,然后是z。
在开放寻址中:table[i] = x、table[i+1] = y、table[i+2] = z。
现在,假设您要删除 x,并将其设置回 NULL。
稍后你搜索z时,你会发现hash(z) = i和table[i] = NULL,你会返回一个错误的答案:z不在表格中。
要克服这个问题,您需要使用特殊标记设置table[i],以指示搜索功能继续查看索引i+1,因为那里可能存在其哈希也是i 的元素。
【讨论】:
insert(y,position)=if (position is occupied) insert(y,position+1); else table[position]=y
在开放式寻址方案中,查找调用一系列探测,直到找到键或找到空槽。
如果一个键涉及多个探针的链,如果沿链的某处移除其他键之一,则它将丢失(找不到),留下一个需要垫脚石的空槽。
通常的解决方案是通过将其插槽标记为可重用但实际上不为空来删除密钥。换句话说,添加了一个替代垫脚石,这样就不会缩短到其他键的探测链。
希望这有助于您的理解。
【讨论】:
从线性探测的开放寻址哈希表中删除很简单。多年来,维基百科哈希表页面上都有它的伪代码。我不知道为什么不再存在,但这里有一个永久链接,可以追溯到它以前的时间: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 的参考。我相信这与插入具有完全相同的性能特征。
这种删除方法比常用的“标记已删除并偶尔重新散列所有内容”要好,因为上述方法是常数时间而不是摊销常数时间。如果您有一个包含一百万个要添加和删除的项目的哈希表,在“标记已删除”方法中,偶尔的添加或删除将比之前和之后的时间长一百万倍 - 这不是良好的性能特点。
【讨论】: