AVL Tree

AVL Tree是指符合平衡条件的二分查找树。它能保证树的深度是logN,最简单的当然是根节点的左右子树高度一样,但这个方式显然不能让树变浅。
其他的平衡条件则要求每一个节点的左右子树都必须有相同的高度,但这就要求必须有2k-1个节点才可以满足,这太过于死板。
AVL Tree类似于二分查找树,它要求每一个节点的左右高度最多可以相差1。通常AVL树的高度可以通过:
计算,近似计算的话一般仅仅比多一点点。
对于AVL Tree的节点和高度关系,举例来说高度为9的AVL树,一般左子树可以为8高度,右子树则为7深度。因此AVL树最小节点数量S(h)可以表示为:
则高度为9的AVL树,最少需要有143个节点。
AVL树所有的操作都可以在时间复杂度完成,但当我们进行插入或者删除操作时,可能会破坏树的平衡条件,为了恢复操作之前的平衡条件,我们需要一种简单的操作来改变这个树,这个操作称之为旋转。
在插入操作后,只有那些在插入节点到根节点路径上的节点会发生平衡改变,我们可以在这个路径上找到那些破坏平衡的节点,然后修复它。我们假设需要被再平衡的节点为A,由于任何节点最多有两个孩子,并且不平衡的前提是左右子树差距超过2,很容易总结为4种类型:

  1. 在A的左子树的左孩子插入
  2. 在A的右子树的左孩子插入
  3. 在A的左子树的右孩子插入
  4. 在A的右子树的右孩子插入
    1、4和2、3是对称关系,所以理论上来说就是两种类型,第一种是发生在外侧(左左、右右) ,需要被单旋转再平衡。第二种类型发生在内侧(左右,右左)需要较为复杂的双旋转。

单旋转

AVL Tree_第1张图片
单旋转

如图表示单旋转来平衡第一种类型。k2(数字为节点大小的递增)节点破坏了AVL的平衡条件(左子树比右子树大了2),通常我们会将X提升一个级别,让Z降低一个级别。我们可以想象当我们拎起k1节点时,由于重力原因其他节点自然的下垂,由于k2>k1,k2就会变为新树的右节点;对于Y来说由于它在k1和k2之间,会被移动到k2的左子树。
通过这个操作后,修改几点link。我们就可以得到一个新的满足平衡的二分查找树。

双旋转

AVL Tree_第2张图片
双旋转.png

对于第二种情况来说,单旋转是无法满足的,如果你让k1当根节点,那么k2就会成为k3的左子树,不平衡问题依然存在。因此我们只能让k2当根节点,并强制的让k1和k3成为k2的左右子节点,k2的孩子节点也自然的移动到应该在的位置,我们可以想象,我们将k2旋转了两次旋转到了根节点,因此这种方法也叫双旋转。我们也发现了和单旋转类似的树的高度也恢复到了之前平衡时的高度。

总结

让我们来总结一下,当我们向AVL树T插入一个元素X的时候,我们递归的判断最终将X插入适当的节点,我们称这个子树为TLR。假如TLR的高度没有发生变化,那我们就完成了插入。否则,假如新增的高度破坏了T的平衡,我们根据X和T和TLR的大小关系来决定是单旋转还是双旋转。当X大于或者小于T和TLR时则单旋转,当X在T和TLR之间时则双旋转。

代码实现

首先我们需要一个AvlNode类,我们还是用内部静态类的方式来实现。

    private static class AvlNode {
        AnyType element;
        AvlNode left;
        AvlNode right;
        int height;

        AvlNode(AnyType theElement) {
            this(theElement, null, null)
        }

        AvlNode(AnyType theElement, AvlNode lt, AvlNode rt) {
            element = theElement;
            left = lt;
            right = rt;
            height = 0;
        }
    }

我们也需要能够返回节点高度的方法,需要注意当节点为空时返回-1。

    /**
     * Return the height of node t, or -1, if null.
     */
    private int height(AvlNode t) {
        return t == null ? -1 : t.height;
    }

然后我们实现一个插入方法,插入的最后我们需要调用一下balance方法,防止插入操作后出现不平衡的情况。

    private AvlNode insert(AnyType x, AvlNode t) {
        if (t == null) {
            return new AvlNode(x, null, null)
        }

        int compareResult = x.compareTo(t.element);

        if (compareResult < 0) {
            t.left = insert(x, t.left);
        } else if (compareResult > 0) {
            t.right = insert(x, t.right);
        } else
            ;
        return balance(t);

    }

balance方法则根据情况选择单旋转还是双旋转,更新树的高度,返回最终的平衡树,可以看到我们会判断该节点的左右子树高度差是否满足平衡,如果不满足就判断是左左、右右的第一种单旋转,还是左右、右左的第二种双旋转情况,最后更新该节点的高度,返回根节点。

    private static final int ALLOWED_IMBALANCE = 1;
    private AvlNode balance(AvlNode t) {
        if(t == null) {
            return t;
        }

        if(height(t.left) - height(t.right) > ALLOWED_IMBALANCE) {
            if(height(t.left.left) >= height((t.left.right))) {
                t = rotateWithLeftChild(t);
            } else {
                t = doubleWithLeftChild(t);
            }
        } else {
            if(height(t.right) - height(t.left) > ALLOWED_IMBALANCE) {
                if(height(t.right.right) >= height(t.right.left)) {
                    t = rotateWithRightChild(t);
                } else {
                    t = doubleWithRightChild(t);
                }
            }
        }

        t.height = Math.max(height(t.left), height(t.right)) + 1;
        return t;
    }

单旋转两种情况,如上文所述,我们需要移动一些链接,代码如下:

    private AvlNode rotateWithLeftChild(AvlNode k2) {
        AvlNode k1 = k2.left;
        k2.left = k1.right;
        k1.right = k2;
        k2.height = Math.max(height(k2.left), height(k2.right)) + 1;
        k1.height = Math.max(height(k1.left), k2.height) + 1;
        return k1;
    }
    
    private AvlNode rotateWithRightChild(AvlNode k1) {
        AvlNode k2 = k1.right;
        k1.right = k2.left;
        k2.left = k1;
        k1.height = Math.max(height(k1.left), height(k1.right)) + 1;
        k2.height = Math.max(height(k2.right), k1.height) + 1;
        return k2;
    }

双旋转根据前面的总结,顾名思义就是两次单旋转,我们就是把k2节点向上旋转了两次。

    private AvlNode doubleWithLeftChild(AvlNode k3) {
        k3.left = rotateWithRightChild(k3.left);
        return rotateWithLeftChild(k3);
    }
    
    private AvlNode doubleWithRightChild(AvlNode k1) {
        k1.right = rotateWithLeftChild(k1.right);
        return rotateWithRightChild(k1);
    }

最后,我们还需要考虑删除操作,和插入操作类似它也会导致破坏平衡的出现。并且在删除时,我们也要考虑删除的是根节点还是叶子节点,如果是叶子节点可以直接删除,如果有左右子节点,我们一般会去右子树找最小的点作为新的根节点,当然也可以从左子树找最大的点。

    private AvlNode remove(AnyType x, AvlNode t) {
        if(t == null) {
            return t;
        }
        
        int compareResult = x.compareTo(t.element);
        if(compareResult < 0) {
            t.left = remove(x, t.left);
        } else if(compareResult > 0) {
            t.right = remove(x, t.right);
        } else if(t.left != null && t.right != null) {
            t.element = findMin(t.right).element;
            t.right = remove(t.element, t.right);
        } else {
            t = (t.left != null) ? t.left : t.right;
        }
        return balance(t);
    }

你可能感兴趣的:(AVL Tree)