Algorithm 哈希表:为什么在开放寻址方案中很难删除

Algorithm 哈希表:为什么在开放寻址方案中很难删除,algorithm,hashtable,hash,Algorithm,Hashtable,Hash,我试图理解开放寻址方法。我参考了T.H.Cormen关于这个主题的书,书中指出删除在开放寻址中是困难的。我完全被这段话困住了: 从开放地址哈希表中删除是困难的。当我们从插槽i中删除密钥时,我们不能简单地通过在插槽中存储NIL来将该插槽标记为空。这样做可能无法检索任何键k,在插入该键的过程中,我们探测了插槽i,发现它已被占用 我不明白。请举例说明。假设hash(x)=hash(y)=hash(z)=i。假设先插入x,然后插入y,然后插入z。 在开放寻址中:表[i]=x,表[i+1]=y,表[i+2

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

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

我不明白。请举例说明。

假设
hash(x)=hash(y)=hash(z)=i
。假设先插入
x
,然后插入
y
,然后插入
z

在开放寻址中:
表[i]=x
表[i+1]=y
表[i+2]=z

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

稍后搜索
z
时,您将发现
hash(z)=i
table[i]=NULL
,您将返回错误答案:
z
不在表中


为了克服这一点,您需要使用一个特殊的标记来设置
表[i]
,该标记指示搜索函数继续查看索引
i+1
,因为在开放寻址方案中,可能存在其哈希值也是
i

的元素,查找调用一系列探测,直到找到密钥或找到空插槽

如果一把钥匙涉及多个探头的链条,那么如果在链条上的某个地方取下另一把钥匙,留下一个需要垫脚石的空槽,钥匙就会丢失(无法找到)

通常的解决方案是删除密钥,方法是将其插槽标记为可继续使用,但实际上不是空的。换句话说,添加了一个替换的踏脚石,这样其他键的探针链就不会被切断


希望这有助于您的理解。

从线性探测的开放寻址哈希表中删除很简单。多年来,维基百科的哈希表页面上一直有关于它的伪代码。我不知道为什么它不再存在了,但这里有一个永久链接,可以追溯到它曾经存在的时候:,为了您的方便,这里是伪代码:

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
功能删除(按键)
i:=查找槽(键)
如果插槽[i]未被占用
return//key不在表中
j:=i
环
j:=(j+1)模数槽
如果插槽[j]未被占用
出口回路
k:=散列(槽[j].key)模num\u槽
如果(j>i和(kj))或
(j
在那一页上也有一些参考文献。我相信这与插入具有完全相同的性能特征


这种删除方法比常用的“删除标记并偶尔重新灰化所有内容”要好,因为上述方法是固定时间而不是摊销固定时间。如果您有一个包含一百万个要添加和删除的项目的哈希表,请使用“标记已删除”方法,偶尔的添加或删除会比之前和之后的添加或删除花费一百万倍的时间,这不是一个好的性能特征。

Amit:非常感谢您的精彩解释。问题:请您解释一下:计算开放寻址所需的探测序列通常使用三种技术:线性探测,二次探测和双重散列。这些技术都保证h(k,1),h(k,2),h(k,m)是0,1,…,的置换,m-每个键k为1。但是,这些技术都不能满足统一哈希的假设,因为它们都不能生成超过m2个不同的探测序列(而不是统一哈希所需的m!)。@Amit如果假设哈希(x)=哈希(y)=哈希(z)=i,那么它怎么能用开放寻址来表示呢。在这种情况下,您需要维护记录的链接列表(或其他类型的链接),对吗?@amit我还有一个。你能试着回答这个问题吗?@Geek:在“开放寻址”中,如果散列(x)=散列(y)=i,并假设x首先插入到索引i中。然后,当你插入y-时,你将它插入到不同的主菜中。最简单的(虽然不是最好的)方法是:
insert(y,position)=if(position)insert(y,position+1);else table[position]=y
提到删除的插槽仍然可以重用的奖励点。如果我错了,请纠正我,但如果只获取一个密钥哈希值,然后比较密钥和插槽[j]的哈希值是否相等,不是更简单吗?我要做的就是找到最后一个被占用的插槽(从j=I+1开始),该插槽的哈希值与key相同,然后将其移动到第I个插槽中。您有一个单一的移动操作,并计算最多num_次的哈希冲突-复杂性与contains(key)和insert(key)操作相同。当所有插槽都被使用或标记时,搜索不存在的元素将永远持续,或者至少遍历整个表,最后得出它不存在的结论。如果程序运行足够长的时间,这种情况就必须发生。我不明白为什么它会永远持续下去。首先找到下一个空槽,从那里向后搜索可能散列为i的条目。如果找到(插槽j),则将其放入第i个插槽。然后对插槽j重复此过程,直到找不到匹配项。当然,最坏的情况是扫描所有内容,但假设负载系数足够低,这应该在O(1)时间内执行。