【问题标题】:Optimizing binary tree inserts to O(1) with hash map for write heavy trees使用哈希映射优化二叉树插入到 O(1) 以写入重树
【发布时间】:2010-12-24 03:57:15
【问题描述】:

首先,我假设我在考虑这个问题时错过了一些重要的事情,但我仍然想发布它,看看我是否真的没有错过任何东西,继续......

我有一个写得很重的二叉树(写和读之间大约 50/50),今天在回家的路上我正在考虑优化它的方法,特别是使写入更快 - 这就是我想出的.

考虑到将 x 添加到树 T 的操作 add(T, x) 首先包含 find(T, x) 以查看 x 是否已经存在,在这种情况下它不会返回父级,因此我们可以添加它而不是父母之一的空叶子。

如果我们在添加操作中添加一个哈希表作为中间缓存,那么当我们调用 add(T, x) 时,真正发生的是 x 被哈希并插入到哈希映射 M 中。就是这样。当我们在其他地方要求 find(T, x) 时进行优化,现在当我们搜索树时,我们将来到叶节点,因为 x 还没有插入树(它只存在于哈希映射 M 中) ,我们散列 x 并将其与 M 中的键进行比较,以查看它是否应该在树中。如果在 M 中找到它,那么我们将它添加到树中并从 M 中删除它。

这将消除对 add(T, x) 的 find(T, x) 操作,并将其简化为 add(M, x),即 O(1)。然后(ab)——使用我们第一次查找节点时执行的find(T, x)操作来插入它。

【问题讨论】:

  • 这将需要您维护 2 个结构,并应用足够的散列算法以避免大型结构中的冲突。你看过这个额外的开销吗?
  • @astander:不,我没有,这就是为什么我在这里询问它是否有任何明显的缺陷,我可以在我选择的平台中使用内置的哈希表(. NET),这应该足够好。并且不难检测到冲突(如果我尝试在 M 中的该哈希上插入一个已经存在的值,比较这两个值,如果它们不相等,则在第二个冲突元素上进行正常的二进制插入)
  • 另外,您可能想要检查 N 个添加和 N 个查找的复杂性(假设为 50/50),而不是单个添加或查找。我认为你在拖延工作而不是优化工作。
  • @Kathy: 可能是,但是由于键(在这种情况下为字符串)上的比较操作非常繁重(我可以使用非常简单的+快速哈希算法,因为冲突无关紧要)感觉我可以节省很多,尤其是在较大的树上。
  • 谨防“碰撞无关紧要”。使用您描述的算法,您的哈希表不允许丢失绑定,因此您不能使用传统缓存中使用的固定长度存储桶。当有很多冲突时,您必须有可扩展的存储桶,这些存储桶会退化为 O(n) 关联列表。

标签: language-agnostic optimization data-structures hashtable binary-tree


【解决方案1】:

如果您需要一个具有 O(1) 次插入和大约 O(n) 次摊销有序迭代的结构,我遇到了同样的问题:

Key-ordered dict in Python

在我的案例中,答案(保留哈希和部分排序的列表,并使用部分排序结构友好的排序,如 TimSort)在实践中非常有效。

【讨论】:

    【解决方案2】:

    为什么不对所有内容都使用哈希表并完全省略二叉树?

    这完全取决于您首先使用二叉树的原因。如果您选择二叉树来增强共享,那么您将失去哈希表缓存,因为哈希表不是共享的。

    缓存也没有让比较两张地图变得更容易。

    编辑:

    如果利用树的特殊性的操作很少见(您提到利用 RB 树已排序的事实),另一方面,如果您经常查找最近添加的键,或者替换最近添加的键的值,用另一种结构实现的小型缓存可能是有意义的。您还可以考虑使用哈希表表示,偶尔转换为树。

    这个缓存层的额外复杂性可能意味着您在实践中没有获得任何时间,或者不足以偿还拥有这样一个 ad-hoc 数据结构的技术债务。

    【讨论】:

    • 嗯,我使用二叉树,因为它是排序的。树(本例中为 RB-tree)用作经常更新的排序索引。
    猜你喜欢
    • 2020-11-28
    • 2015-02-13
    • 2019-08-26
    • 1970-01-01
    • 2010-10-25
    • 1970-01-01
    • 2022-01-14
    • 2016-01-17
    相关资源
    最近更新 更多