是否总是可以使用树旋转将一个BST转换为另一个BST?

tem*_*def 7 algorithm binary-search-tree data-structures tree-rotation

给定一组值,可以存在许多可以由这些值形成的不同的可能二进制搜索树.例如,对于值1,2和3,我们可以从这些值中生成五个BST:

1      1      2      3      3
 \      \    / \    /      /
  2      3  1   3  1      2
   \    /           \    /
    3  2             2  1
Run Code Online (Sandbox Code Playgroud)

许多基于平衡二叉搜索树的数据结构使用树旋转作为重构BST的原语,而不会破坏所需的二进制搜索树不变量.树旋转可用于将节点拉到其父节点之上,如下所示:

                rotate
         u      right           v
        / \     ----->         / \
       v   C                  A   u
      / \       <-----           / \
     A   B      rotate          B   C
                 left
Run Code Online (Sandbox Code Playgroud)

给定一个包含一组值的BST,是否总是可以将该BS​​T转换为任意其他BST用于同一组值?例如,我们是否可以仅使用树旋转将上述五个BST中的任何一个转换为任何其他BST?

tem*_*def 18

您的问题的答案取决于您是否被允许在BST中具有相同的值,这些值可能看起来彼此不同.例如,如果您的BST存储键/值对,则对于相同的键/值对,并不总是可以将这些键/值对的一个BST转换为不同的BST.

这样做的原因是无论执行多少树旋转,BST中节点的顺序遍历都保持不变.因此,如果节点的顺序遍历会以不同方式出现,则无法从一个BST转换为另一个BST.作为一个非常简单的情况,假设您有一个BST,其中包含数字1的两个副本,每个副本都注释了不同的值(例如,A或B).在这种情况下,没有办法使用树旋转将这两棵树变成另一棵树:

       1:a            1:b
         \             \
         1:b           1:a
Run Code Online (Sandbox Code Playgroud)

您可以通过强制使用旋转强制(非常小!)一组可能的树来检查这一点.但是,只需要注意第一个树的顺序遍历给出1:a,1:b并且第二个树的顺序遍历给出1:b,1:a.因此,没有数量的旋转就足以在树之间进行转换.

另一方面,如果所有值都不同,则始终可以通过应用正确数量的树旋转来在两个BST之间进行转换.我将使用关于节点数量的归纳论证来证明这一点.

作为一个简单的基本情况,如果树中没有节点,则只有一个可能的BST保存这些节点:空树.因此,始终可以在两个具有零节点的树之间进行转换,因为起始树和结束树必须始终相同.

对于归纳步​​骤,让我们假设对于具有相同值的0,1,2,...,n个节点的任何两个BST,总是可以使用旋转从一个BST转换为另一个BST.我们将证明,如果任何两个BST由相同的n + 1值组成,则始终可以将第一个树转换为第二个树.

要做到这一点,我们首先要做一个关键的观察.给定BST中的任何节点,始终可以应用树旋转将该节点拉到树的根.为此,我们可以应用此算法:

while (target node is not the root) {
    if (node is a left child) {
        apply a right rotation to the node and its parent;
    } else {
        apply a left rotation to the node and its parent;
    }
}
Run Code Online (Sandbox Code Playgroud)

这样做的原因是,每次使用父节点旋转节点时,其高度都会增加1.结果,在应用了足够多的上述形式的旋转之后,我们可以将根到达树的顶部.

这现在为我们提供了一个非常简单的递归算法,我们可以使用旋转将任何一个BST重塑为另一个BST.这个想法如下.首先,查看第二个树的根节点.在第一棵树中找到该节点(这很简单,因为它是BST!),然后使用上面的算法将其拉到树的根部.此时,我们已将第一个树转换为具有以下属性的树:

  1. 第一个树的根节点是第二个树的根节点.
  2. 第一个树的右子树包含与第二个树的右子树相同的节点,但可能具有不同的形状.
  3. 第一个树的左子树包含与第二个树的左子树相同的节点,但可能具有不同的形状.

因此,我们可以递归地应用相同的算法使左子树具有与第二树的左子树相同的形状,并使右子树具有与第二树的右子树相同的形状.由于这些左右子树每个必须严格不超过n个节点,因此通过我们的归纳假设,我们知道总是可以这样做,因此算法将按预期工作.

总而言之,该算法的工作原理如下:

  1. 如果两棵树都是空的,我们就完成了.
  2. 在第一个树中查找第二个树的根节点.
  3. 应用旋转以将该节点提升到根节点.
  4. 递归地重塑第一棵树的左子树,使其具有与第二棵树的左子树相同的形状.
  5. 递归地重塑第一棵树的右子树,使其具有与第二棵树的右子树相同的形状.

要分析此算法的运行时间,请注意应用步骤1 - 3最多需要O(h)步骤,其中h是第一棵树的高度.每个节点都会被引导到某个子树的根部一次,所以我们总共执行O(n)次.由于n节点树的高度永远不会大于O(n),这意味着该算法最多需要O(n 2)时间才能完成.它可能会做得更好(例如,如果两棵树已经具有相同的形状,那么它会在时间O(n)中运行),但这给出了一个很好的最坏情况界限.

希望这可以帮助!


小智 5

对于二叉搜索树,这实际上可以在O(n)中完成。

任何树都可以被“拉直”,即放入所有节点要么是根要么是左子节点的形式。

这种形式是唯一的(从根向下读给出元素的顺序)

一棵树被拉直如下:

  • 对于任何右孩子,围绕其自身进行左旋转。这会将右子节点的数量减少1,因此树在O(n)次旋转中被拉直。

如果 A 可以在O(n)次旋转中直化为 S,并且 B 在O(n)次旋转中直化为 S,那么由于旋转是可逆的,因此可以在O(n)次旋转中将 A -> S -> B 旋转。