你怎么知道在AVL树的哪里执行旋转?



我是自学AVL树的,我理解它背后的基本思想,但我只是想确保我的直觉是有效的:

我将检查它的左旋转-

所以,下面的情况很简单:

      8
     / 
    7   10
   /
  6
 /
3

当我们加上3时,树重新平衡为:

    8
   / 
  6   10
 / 
3   7

但是旋转是基于3的加法还是子树的不平衡根植于7?它甚至是基于根植于8的树的不平衡吗?

在我看来,下面的例子是事情变得有点棘手的地方:

      9
     / 
    7   10
   / 
  6   8
 /
3

所以,在这种情况下,7处的子树在加上3时很好,所以子树不需要旋转。然而,9的树由于增加了3而变得不平衡,所以我们以9为旋转的基础。我们得到:

      7
     / 
    6   9
   /   / 
  3   8   10

那么,在编写我的代码时,我很快就会写,下面的代码,从小的子树开始到更大的子树,能做到吗?

伪代码:

function balanceTree(Node n){
  if (n is not null){
    balanceTree(n.rightchild);
    balanceTree(n.leftchild);
  }
  if (abs(balanceFactor(n))>1){
    rotateAsNeeded(n);// rotate based on balance factor
  }
}

提前感谢!

您发布的伪代码将正确地平衡树。也就是说,它的效率太低,不实用——请注意,您正在递归地探索整个树,试图进行再平衡操作,这将使所有插入和删除花费O(n)时间,消耗掉拥有平衡树的所有效率收益。

AVL树背后的思想是,全局重新平衡树可以通过迭代地应用局部旋转来完成。换句话说,当你进行插入或删除时,需要进行树旋转,这些旋转不会出现在树中的随机点上。它们总是在插入或删除节点时沿着访问路径出现。

例如,您想要将值3插入到这个树中:

      9
     / 
    7   10
   / 
  6   8

让我们首先写出与每个节点相关的平衡因子的差异(AVL树节点存储这些信息至关重要,因为这使得有效地执行插入和删除成为可能):

           9(+1)
         /       
       7 (0)    10 (0)
      / 
  6(0)   8(0)

现在让我们看看插入3会发生什么。这里是3:

           9(+1?)
          /       
        7 (0?)    10 (0)
       /   
   6(0?)   8(0)
   /
 3(0)

注意,我已经用?标记了访问路径上的所有节点,因为我们不再确定它们的平衡因子是什么。由于我们为6插入了一个新的子节点,这将6节点的平衡因子更改为+1:

           9(+1?)
          /       
        7 (0?)    10 (0)
       /   
   6(+1)   8(0)
   /
 3(0)

同样,7的左子树的高度也增加了,所以它的平衡因子应该增加:

           9(+1?)
          /       
        7 (+1)    10 (0)
       /   
   6(+1)   8(0)
   /
 3(0)

最后,9的左子树增长了1,得到如下结果:

           9(+2!)
          /       
        7 (+1)    10 (0)
       /   
   6(+1)   8(0)
   /
 3(0)

这里我们发现9有一个平衡因子+2,这意味着我们需要做一个旋转。查阅维基百科关于所有AVL树旋转的表格,我们可以看到我们的平衡因子是+2而左子结点的平衡因子是+1。这意味着我们做一个右旋转,把7拉到9上面,如下所示:

        7(0)
       /   
   6(+1)     9(0)
   /       /   
 3(0)    8(0)   10 (0)

等voilà !树现在是平衡的。

注意,当我们执行这个修复过程时,我们不必遍历整个树。相反,我们所需要做的就是沿着访问路径查看并检查那里的每个节点。通常,在实现AVL树时,插入过程将执行以下操作:

  • 如果树为空:
    • 插入平衡因子为0的节点
    • 返回树的高度增加了1。
  • 否则:
    • 如果插入的值与当前节点匹配,则不做任何操作。
    • 否则,递归地将节点插入到适当的子树中,并获得树高度变化的量。
    • 根据子树高度的变化量更新该节点的平衡因子
    • 如果这要求一系列的旋转,执行它们。
    • 返回树的高度变化。

由于所有这些操作都是局部的,因此完成的总工作量完全基于访问路径的长度,在这种情况下是O(log n),因为AVL树总是平衡的。

希望这对你有帮助!


PS:你最初的例子是这棵树:

      8
     / 
    7   10
   /
  6
 /
3

注意,这个树实际上不是一个合法的AVL树,因为根节点的平衡因子是+2。如果您始终使用AVL算法维护树平衡,您将永远不会遇到这种情况。

最新更新