从哈希表中删除条目的最佳方法

时间:2008-11-10 23:21:13

标签: data-structures hashtable

从使用线性探测的哈希表中删除条目的最佳方法是什么?一种方法是使用标志来表示已删除的元素?有没有比这更好的方法?

6 个答案:

答案 0 :(得分:32)

一种简单的技巧是:

  1. 查找并删除所需元素
  2. 转到下一个桶
  3. 如果存储桶为空,请退出
  4. 如果存储桶已满,请删除该存储桶中的元素,然后使用常规方法将其重新添加到哈希表中。在重新添加之前必须删除该项目,因为该项目很可能会被添加回其原始位置。
  5. 重复步骤2.
  6. 这种技术可以让你的桌子保持整洁,但代价是删除速度稍慢。

答案 1 :(得分:14)

这取决于你如何处理溢出以及(1)被删除的项目是否在溢出槽中,以及(2)如果除了项目之外还有溢出项目,它们是否具有要删除的项目或可能的其他哈希键。 [忽略这种双重条件是删除实现中常见的错误来源。]

如果碰撞溢出到链表中,那很容易。您要么弹出列表(可能已经空了),要么删除链接列表中间或末尾的成员。这些都很有趣,并不是特别困难。可以进行其他优化以避免过多的内存分配和释放,从而提高效率。

对于线性探测,Knuth建议一种简单的方法是将插槽标记为空,删除或占用。将删除的占用者插槽标记为已删除,以便通过线性探测溢出将跳过它,但如果需要插入,则可以填写您通过的第一个删除的插槽[计算机编程艺术,第3卷:排序和搜索,第6.4节哈希,p。 533(ed.2)]。这假设删除相当罕见。

Knuth给出了一个很好的结果,如算法R6.4 [pp。相反,它将单元格标记为空而不是删除,然后通过移动刚刚制作的孔直到它最终靠近另一个孔,找到将表条目移回其初始探针位置的方法。

Knuth警告说,这将移动现有的仍然占用的插槽条目,如果指向插槽的指针被保留在散列表的外部,则不是一个好主意。 [如果您在插槽中有垃圾收集或其他托管引用,则可以移动插槽,因为它是在表外使用的引用,并且引用的插槽位置无关紧要同一个对象在表格中。]

答案 2 :(得分:8)

Python哈希表实现(可以说得非常快)使用虚拟元素来标记删除。随着你的成长或缩小或表格(假设你没有做一个固定大小的桌子),你可以同时丢弃假人。

如果您有权访问副本,请查看Beautiful Code中有关实施的文章。

答案 3 :(得分:3)

我能想到的最佳通用解决方案包括:

  • 如果您可以使用非const迭代器(ala C ++ STL或Java),则应该能够在遇到它们时将其删除。但是,据推测,除非你使用const迭代器或枚举器,否则你不会问这个问题,如果底层集合被修改则会失效。
  • 如您所说,您可以在包含的对象中标记已删除的标记。但是,这不会释放任何内存或减少键上的冲突,因此它不是最佳解决方案。还需要在类上添加可能并不属于那里的属性。如果这对你造成的困扰,或者如果你根本无法向存储的对象添加标志(可能你不控制类),你可以将这些标志存储在一个单独的哈希表中。这需要最长期的内存使用。
  • 在遍历哈希表时,将要删除的项的键推入向量或数组列表中。释放枚举器后,循环访问此辅助列表并从哈希表中删除键。如果你有很多要删除的项目和/或密钥很大(它们不应该是),这可能不是最佳解决方案。
  • 如果您最终要从哈希表中删除的项目多于离开那里的项目,那么创建一个新的哈希表可能会更好,当您遍历原始哈希表时,添加到新哈希仅列出您要保留的项目。然后用新的哈希表替换旧的哈希表。这样可以节省辅助列表迭代,但是如果新的哈希表的项目数量明显少于原始哈希表,那么它可能效率很高,当然,只有当你可以改变对原始哈希表的所有引用时,它才有效。
  • 如果你的哈希表允许你访问它的密钥集合,你可以在一遍中迭代这些密钥并从哈希表中删除项目。
  • 如果您的库中的哈希表或某个帮助程序为您提供了基于谓词的集合修饰符,则可以使用Remove()函数向其传递lambda表达式或函数指针以标识要删除的项目。

答案 4 :(得分:1)

当时间是一个因素时,一种常见的技术是拥有第二个已删除项目表,并在有时间时清理主表。常用于搜索引擎。

答案 5 :(得分:0)

如何增强哈希表以包含链接列表之类的指针? 插入时,如果存储桶已满,则从该存储桶创建指向存储新字段的存储桶。

在从哈希表中删除某些内容时,解决方案将等同于您编写一个从链表中删除节点的函数。