【问题标题】:Concatenating red-black trees连接红黑树
【发布时间】:2011-03-11 18:00:09
【问题描述】:

OCaml 标准库有一个很棒的Set 实现,它使用非常有效的分治算法来计算两个集合的union。我相信它会从一组中获取整个子树(不仅仅是单个元素)并将它们插入到另一组中,并在必要时重新平衡。

我想知道这是否需要 OCaml 使用的 AVL 树中保存的高度信息,或者这是否也可以使用红黑树。例如,连接一对红黑树是否比简单地遍历第二棵树并将其元素附加到第一棵树的末尾更有效?

【问题讨论】:

  • 有人投票以“离题”结束我的问题。 Stack Overflow 上的 RB 树什么时候离题了?!

标签: algorithm data-structures red-black-tree


【解决方案1】:

从您问题的措辞中,我不确定您是否对集合并集或连接或两者感兴趣,或者您是否只对 OCaml 中常见的持久数据结构或临时结构感兴趣。

red-black trees with fingers is described by Heather D. Booth in a chapter from her thesis 的实现。用手指,一棵大小为 n 的红黑树可以在摊销 O(lg (min (p,q))) 时间内分成两棵大小为 p 和 q 的树,两棵大小为 p 和 q 的红黑树可以连接在同一个范围内。此外,可以在分摊 O(1) 时间内在 rb 树的任一端添加或删除元素。通过这些操作,可以实现摊销 O(p lg(q/p)) 时间集并集(对于 p

上述界限在传统意义上是摊销的。对于像 OCaml 这样的函数式语言,人们可能希望在持久使用数据结构时具有适用的界限。我不认为 Booth 的描述会在持久使用这些树时达到所有这些界限。例如,在手指处插入可能需要 ω(1) 重新着色。这可以通过lazy recolorings discussed in Driscoll et al.'s "Making Data Structures Persistent" 解决。

另一方面,我认为 Booth 的分析可能表明即使持续使用,连接仍然是 O(lg (max (p,q)))。我对 set union bound 不太乐观。

具有渐近最优时间界限的集合操作在函数设置中是可能的。那些described by Hinze & Paterson 在摊销(但持久)的意义上达到了界限,treaps described by Blandford & Blelloch achieve the bounds in a randomized sense 和那些described by Kaplan & Tarjan 在最坏的情况下实现了它们。后者还提供 O(lg lg (min(p,q))) 连接,尽管 Hinze 和 Paterson 对这种说法持怀疑态度。这些树不是您的问题的直接答案,这是特定于红黑树的问题,但它们希望能提供一种可能的味道,H&P 论文包括代码和has been verified correct using Coq,它可以提取到 OCaml 代码。

另外两个您可能感兴趣的指针:Brodal et al. presented search trees with O(lg n) find, insert, and delete and O(1) concat even in a functional setting。另外,Atallah et al. claim to describe a red-black tree that has amortized O(1) concat (presumably ephemerally only),但Buchsbaum and Goodrich claim that there are several flaws in that structure

关于红黑树效用的最后一点说明:在该问题的答案之一的 cmets 中,您说:

红黑树的唯一优点是辅助信息(红色或黑色)每个分支只有 1 位。通过增加高度,您就失去了这种优势,还不如只使用高度平衡的树。

还有其他优点。例如,计算几何中使用的一些数据结构基于二叉搜索树,但树旋转的成本很高。 Red-black trees can be rebalanced in at most 3 rotations per insert and delete,而AVL trees can take Ω(lg n) rotations for these operationsAs Ralf Hinze noticedOkasaki's rebalancing scheme for red-black trees(代码在MLHaskellJava, and Ada)不提供相同的界限,并且最终会在插入时进行 Ω(lg n) 旋转。 (冈崎不存在删除。)

此外,可以存储高度平衡的搜索树(甚至是 AVL 树),以便每个节点仅使用一位平衡信息。有些树在每个节点上只有两个可能的平衡位置,例如单边高度平衡树,但是每个节点最多有四个可能的平衡位置的树可以在每个子节点中存储一位平衡信息,如initially explained by Brown 和以后的@ 987654338@

编辑:

在回答您问题末尾的具体查询时,这里描述了一种用于连接两棵红黑树的算法。它需要 O(lg(max(|L|,|R|))) 时间,这对于我上面描述的渐近最优联合时间来说太长了。作为比较,我希望the "join" implementation for AVL sets in OCaml's stdlib 获得 O(h1-h2) 性能,其中 h1 是较高树的高度,尽管它实际上连接了两个 AVL 树,给定一个适合它们之间的元素,而下面的算法必须找到并从其参数之一中删除该迫击炮元素。您可以通过仅将元素存储在叶子中来避免这种情况,就像在 B+ 树中一样,但这会带来空间损失,即必须在非叶子节点中保留一堆指向元素的指针来指导搜索。无论如何,它不会像 OCaml 标准库中的 AVL 连接代码那样使相同高度的树的连接时间保持不变,因为您仍然需要计算每棵树的黑色高度,如下所述。

给定两个非空的红黑树 L 和 R,我们将生成一个新的红黑树,它是 L 和 R 的串联。这将花费与 O(lg (max(|L|,|R|))),其中 |L|表示L中的节点数。

首先,从L,c中删除最大的元素。接下来,找到 L 和 R 的黑色高度。“黑色高度”是指从 根到叶子。通过红黑树不变量,这在任何给定树的所有路径上都是恒定的。调用 L 的黑色高度 p 和 R 的黑色高度 q,并假设 w.l.o.g. p≤q。

从 R 的根开始,跟随左孩子直到到达一个高度为 p 的黑色节点 R'。用根元素 c、左子节点 L 和右节点创建一棵新的红树 C 孩子R'。由于 L 本身是一棵红黑树,所以它的根是黑色的,在 C 或 C 以下不违反颜色不变量。此外,C 的黑色高度 是p。

但是,我们不能简单地将 C 拼接回 R 来代替 R'。首先,如果 p = q,R' 是 R,但 C 有一个红色的根。在这种情况下,只需将 C 的根重新着色为黑色。这是你的 新的连接树。

其次,如果 R' 不是根,它可能有一个红色的父。红色的父母不允许有红色的孩子,所以我们必须重新平衡。这里我们只应用冈崎的再平衡 沿着 R'(现在被 C 替换)和 R 的根之间的脊椎设计。

有两种可能的情况。如果 C 没有祖父母,则将 C 的父母涂成黑色。树现在有效。

如果 C 有一个祖父母,它必须是黑色且黑色高度为 p+1,因为 C 的父母是红色的。用一棵新的红树替换 C 的祖父母,它的根是 C 的父母的根,即 左孩子是 C,重新着色为黑色,右孩子是一棵由 C 的兄弟姐妹、C 的祖父母的根和 C 的叔叔组成的黑色树,在 那个命令。这不会增加 C 祖父母的黑色高度,但会将其颜色更改为红色,这可能使其成为红色的根或红色子代 父级,所以我们必须再次重新平衡,依此类推直到树上

  • 求两棵树的黑色高度:O(lg |L|) + O(lg |R|)
  • 向下追踪 R 到正确的位置:O(lg |R| - lg |L|)
  • 一直旋转到根:O(lg |R| - lg |L|)

这些都不大于 O(lg |R| + lg |L|) = O(lg (max(|L|,|R|)))

要使这个 O(lg (min(|L|,|R|))),首先反转脊椎指针。那么你就不需要大树的黑色高度了,你只需要计算黑色的脊椎节点,直到一棵树用完脊椎。然后,使用原始(不是 Okasaki 的)重新平衡方案来确保您只重新平衡 O(1) 个节点。最后,标记不需要重新平衡的脊椎的其余部分,以便稍后必要时进行延迟重新着色。

【讨论】:

  • Upvote 已解决您的业力问题。你有机会回来编辑吗?这似乎是一个潜在的好答案,可以通过可点击的链接显着改善。
  • @spong:是的,我现在就去做。谢谢你提醒我。
【解决方案2】:

在连接而不是用每个节点中的高度信息扩充树时,可以比 O(log^2(n)) 做得更好。 您可以在 2* [log(n1) + log(n2)] 中连接,其中 n1 和 n2 表示要连接的树中的节点数。计算 O(log(n)) 的高度后,在向下树时使用每个节点中的 Balance 信息来找到正确的连接点!

【讨论】:

    【解决方案3】:

    由于您似乎在谈论 Concatenate + 添加到末尾,因此您似乎遇到了以下问题:

    Given two red-black trees T1 and T2, such that keys of T1 <= keys of T2,
    find union of the two.
    

    这称为树上的连接操作,在这种情况下,可以在 O(log n) 时间内完成树的连接,请查看:http://www.cs.tau.ac.il/~wein/publications/pdfs/rb_tree.pdf

    另请查看:http://net.pku.edu.cn/~course/cs101/resource/Intro2Algorithm/book6/chap14.htm,问题 14.2。

    【讨论】:

    • 看起来他们只是在 O(log n) 中通过在每个节点中增加树的高度信息来做到这一点,此时它不再是普通的红黑树。
    • @Jon。即使有了这些信息,我们也可以认为它是一棵红黑树。它不会改变插入/删除等仍然是 O(logn) 并且节点颜色遵循 rb 树不变量的事实。无论如何,我看不出还有什么:-)
    • @Moron:红黑树的唯一优点是每个分支的辅助信息(红色或黑色)只有 1 位。通过增加高度,您就失去了这种优势,还不如只使用高度平衡的树。
    • @Jon:如果 Tarjan 和 Sleator 认为发表一篇关于它的论文足够重要,我不能这么轻易地驳回它 :-)。在任何情况下,如果不保持高度信息并且仍然超过线性时间,您仍然可以在 O(log^2(n)) 时间内完成。
    • @Jon:我没有研究算法的细节,但是当你需要高度时,只需在 O(logn) 时间内重新计算即可。在最坏的情况下,这将使算法 O(log^2(n))。
    【解决方案4】:

    当您将低重叠的树组合在一起时,您可能会有所收获,但通常您必须重新组织节点。平衡时你的情况会更糟,因为可能只有在接触一个节点后旋转的规则。

    【讨论】: