连接/合并/加入两个 AVL 树
Posted
技术标签:
【中文标题】连接/合并/加入两个 AVL 树【英文标题】:Concatenating/Merging/Joining two AVL trees 【发布时间】:2011-01-03 11:29:49 【问题描述】:假设我有两棵 AVL 树,并且第一棵树中的每个元素都小于第二棵树中的任何元素。将它们连接成一棵 AVL 树的最有效方法是什么?我到处搜索,但没有发现任何有用的东西。
【问题讨论】:
谢谢你提出的问题,但我认为它更适合:cs.stackexchange.com 【参考方案1】:假设您可能会破坏输入树:
-
移除左树最右边的元素,并用它构造一个新的根节点,其左孩子为左树,右孩子为右树:O(log n)
确定并设置该节点的平衡因子:O(log n)。在(临时)违反不变量的情况下,平衡因子可能超出范围 -1, 0, 1
旋转以使平衡因子回到范围:O(log n) 旋转:O(log n)
因此,整个操作可以在 O(log n) 内完成。
编辑:再想一想,以下算法中的旋转更容易推理。它也很可能更快:
-
确定两棵树的高度:O(log n)。
假设右树更高(另一种情况是对称的):
从
left
树中移除最右边的元素(必要时旋转并调整其计算高度)。让n
成为那个元素。 O(log n)
在右侧树中,向左导航,直到到达其子树最多比 left
高 1 的节点。让r
成为那个节点。 O(log n)
用值为 n 的新节点和子树 left
和 r
替换该节点。 O(1)
通过构造,新节点是 AVL 平衡的,其子树 1 高于 r
。
相应地增加其父级的余额。 O(1)
并像插入后一样重新平衡。 O(log n)【讨论】:
你确定吗? (你可能很容易是正确的,但我只是好奇。)假设左边的树比右边的树小得多,也就是浅得多。为什么 O(log n) 旋转会恢复 AVL 属性?你如何决定在哪里轮换? Dale 所说:AVL 树的通常旋转选择允许使用 O(log n) 旋转将大小为 2 的不平衡纠正回 [-1,1] 范围。您需要一个选择旋转的新方案,以纠正任意不平衡。由于那是我永远记不起的 AVL 树的一部分,并且每次都必须查找,我希望即使旋转的选择很明显,对我来说也不明显:-) 好点。我发现证明另一种算法更容易(参见我编辑的答案)。 我花了一些时间来解析您所说的“用 (left,n,r) 替换该节点”的意思。考虑改写。 我相信您的回答有错误的细节。在上一个算法的第三步中,向左导航,直到到达其子树与左树具有相同高度的节点。设 r 为该节点。这并不总是可能的,counterexample here。执行此步骤的正确方法是两个查找高度为h
或h+1
的子树,其中h
是左侧树的高度。【参考方案2】:
一个超简单的解决方案(无需对树之间的关系进行任何假设即可工作)是这样的:
-
将两棵树合并到一个合并数组中(同时迭代两棵树)。
从数组构建 AVL 树 - 将中间元素作为根,并递归应用于左右两半。
这两个步骤都是 O(n)。它的主要问题是它需要 O(n) 额外的空间。
【讨论】:
第一步不是O(n log(n))吗? 主要问题是它在时间上不是对数的。【参考方案3】:我读到的这个问题的最佳解决方案可以找到here。如果您更正此问题,则非常接近meriton 的答案:
在算法的第三步中,向左导航,直到到达子树与左树高度相同的节点。这并不总是可能的,(参见反例图像)。执行此步骤的正确方法是两个查找高度为h
或h+1
的子树,其中h
是左树的高度
【讨论】:
【参考方案4】:我怀疑您只需要走一棵树(希望是较小的树),然后将其中的每个元素单独添加到另一棵树。 AVL 插入/删除操作并非旨在一次处理添加整个子树。
【讨论】:
我有一种感觉,可以线性完成。使用朴素的方法需要 O(n log n) 时间。 这需要 O(n log n) -> 比 Meriton 的解决方案慢得多 @meriton 的解决方案确实非常好,但它假设 (a) 一棵树中的每个节点都严格小于另一棵树中的每个节点,(b) 您可以访问原始 avl 树数据结构,并且 (c) 可以直接在树节点上执行旋转。如果 (a) 不成立,或者您无法使用低级树操作(很可能是因为您使用的是 avl 库),那么您可能不得不退回到简单地将每个节点从较小的树插入到较大的。以上是关于连接/合并/加入两个 AVL 树的主要内容,如果未能解决你的问题,请参考以下文章
java 此实现使用AVL平衡树。允许树中任何节点的高度为两个子树的最大差异为一。