从您问题的措辞中,我不确定您是否对集合并集或连接或两者感兴趣,或者您是否只对 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 operations。 As Ralf Hinze noticed、Okasaki's rebalancing scheme for red-black trees(代码在ML、Haskell、Java, 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) 个节点。最后,标记不需要重新平衡的脊椎的其余部分,以便稍后必要时进行延迟重新着色。