【AVL自平衡的二叉搜索树模板——面试高频问题之一】


一、什么是AVL?

AVL是一种自平衡的二叉搜索树,它以其发明者Adelson-Velsky和Landis的名字命名。AVL树是一种高度平衡的二叉搜索树,它的特点是任意节点的左子树和右子树的高度差不超过1。

AVL树的平衡性是通过在插入或删除节点时进行旋转操作来维持的。旋转操作可以分为左旋和右旋两种类型,通过调整节点的位置来保持树的平衡性。当插入或删除节点导致树的平衡被破坏时,AVL树会自动进行旋转操作,使得树重新达到平衡状态。

由于AVL树的平衡性,它在最坏情况下的搜索、插入和删除操作的时间复杂度都是O(log n),其中n是树中节点的数量。这使得AVL树在实际应用中具有高效的性能。

需要注意的是,由于AVL树需要维护平衡性,所以在插入和删除操作时需要进行额外的平衡调整,这可能会导致一些性能开销。(即AVL树更适合存储之后,不再改变的数据,适合用于搜索查找,不适合插入和删除)因此,在某些特定的应用场景下,可能会选择其他类型的自平衡树,如红黑树,来权衡性能和平衡性的需求。

总结来说

AVL树是一种自平衡的二叉搜索树,具有以下性质:

  1. 平衡性:对于AVL树的任意节点,其左子树和右子树的高度差(平衡因子)不超过1。即对于每个节点,其左子树的高度和右子树的高度最多相差1。

  2. 二叉搜索树性质:AVL树是一棵二叉搜索树,即对于每个节点,其左子树中的所有节点的值都小于该节点的值,而右子树中的所有节点的值都大于该节点的值。

  3. 高度平衡性:AVL树的高度是最小的,也就是说,AVL树的高度是O(log n),其中n是树中节点的数量。

  4. 自平衡性:当向AVL树中插入或删除节点时,会通过旋转操作来保持树的平衡。旋转操作包括左旋和右旋,通过调整节点的位置来保持树的平衡性。

  5. AVL树的平衡性保证了在最坏情况下,搜索、插入和删除操作的时间复杂度都是O(log n)

二、代码模板

具体的解释全部写进代码,笔者能力有限,如果不能理解,可以去下面链接🔗去理解思想,然后再回头重新看代码注释

B站韩顺平老师的讲解,笔者和韩老师代码有差异,但是思想相同

读者能掌握韩老师的代码,自然也能理解笔者的。

class Node {
    int value;// 节点值
    int height;// 该节点的高度
    Node left;// 左子树
    Node right;// 右子树

    /**
     * 初始化,为 AVL的 insert()作准备
     * @param value
     */
    public Node(int value) {
        this.value = value;
        this.height = 1;
        this.left = null;
        this.right = null;
    }
}

public class AVLTree {
    private Node root;// 根节点

    /**
     * public
     * 插入方法(供外部使用)
     * @param value 待插入的值
     */
    public void insert(int value) {
        // 调用内部的具体实现方法
        this.root = insertNode(this.root, value);
    }

    /**
     * private
     *  采用递归的形式,修改每一个分支让其达到平衡后,返回新的头结点
     * @param root
     * @param value
     * @return
     */
    private Node insertNode(Node root, int value) {
        if (root == null)// 如果没有节点直接新建
            return new Node(value);
        if (value < root.value) // 小于当前节点,就去左边继续
            root.left = insertNode(root.left, value);
        else if (value > root.value)// 大于当前节点,就去右边继续
            root.right = insertNode(root.right, value);
        else// 等于当前节点,说明该 value已经存在,没有必要添加,直接返回该节点
            return root;
        // 计算当前节点的高
        root.height = Math.max(getHeight(root.left), getHeight(root.right)) + 1;
        // 当前节点的平衡因子(左树高 - 右树高 =合法= -1 || 0 || 1)
        int balanceFactor = getBalanceFactor(root);

        // 根据平衡因子的值,进行不同的旋转
        // (一)
        // 满足右单旋的条件(具体的例子在 rightRotate()的注释里)
        if (balanceFactor > 1 && value < root.left.value)
            return rightRotate(root);
        // (二)
        // 满足左单旋的条件(具体的例子在 leftRotate()的注释里)
        if (balanceFactor < -1 && value > root.right.value)
            return leftRotate(root);
        // (三)
        // 满足先左单旋 root.left,再右单旋 root
        /**                例如
         *                  15(root)                                        15(root)
         *                 /  \       先将 root.left即 10左旋                 /  \           再将 root即 15右旋
         * (root.left)  10    20    -------------------->                13    20         -------------------->                    13
         *             /   \                                             /                                                        /   \
         *            5    13                                          10                                                       10     15
         *                /                                          /   \                                                    /   \      \
         *               11                                         5     11                                                 5    11      20
         */
        if (balanceFactor > 1 && value > root.left.value) {
            root.left = leftRotate(root.left);
            return rightRotate(root);
        }
        // (四)
        // 满足先右旋 root.right,再左旋 root
        /**                例如
         *                 15(root)                                           15(root)                                     20
         *                /  \                 先将 root.right即 25右旋        /   \         再将 root即 15左旋                /   \
         *               5    25(root.right) ---------------------->       5     20       -------------------->          15     25
         *                   /  \                                                /   \                                   /  \      \
         *                 20    35                                            18     25                                5    18     35
         *                /                                                             \
         *               18                                                              35
         */
        if (balanceFactor < -1 && value < root.right.value) {
            root.right = rightRotate(root.right);
            return leftRotate(root);
        }
        return root;
    }

    /**
     * public
     * 删除方法(供外部使用)
     * @param value 删除的值
     */
    public void delete(int value) {
        // 调用内部具体实现的方法
        this.root = deleteNode(this.root, value);
    }

    /**
     * private
     * 递归寻找 value右子节点的最小值替换它,然后删除右子树最小值节点
     * @param root
     * @param value
     * @return
     */
    private Node deleteNode(Node root, int value) {
        if (root == null)
            return null;
        if (value < root.value)//  小于去左边找
            root.left = deleteNode(root.left, value);
        else if (value > root.value)// 大于去右边找
            root.right = deleteNode(root.right, value);
        else// 等于的情况
            if (root.left == null || root.right == null)// 左右至少有一个节点为null
                root = (root.left != null) ? root.left : root.right;
            else {
                // 找到右边节点的最小值将其替换
                Node minNode = findMinNode(root.right);
                root.value = minNode.value;
                // 然后将替换过后的节点从二叉树中删除
                root.right = deleteNode(root.right, minNode.value);
            }
        // 删除 minNode过后,该节点为 null,直接返回
        // 或者找不到 value,也直接返回
        if (root == null)
            return null;
        // 和插入节点相同,每次修改过后二叉树,都必须重新计算是否平衡
        root.height = Math.max(getHeight(root.left), getHeight(root.right)) + 1;
        int balanceFactor = getBalanceFactor(root);
        if (balanceFactor > 1 && getBalanceFactor(root.left) >= 0)
            return rightRotate(root);
        if (balanceFactor > 1 && getBalanceFactor(root.left) < 0) {
            root.left = leftRotate(root.left);
            return rightRotate(root);
        }
        if (balanceFactor < -1 && getBalanceFactor(root.right) <= 0)
            return leftRotate(root);
        if (balanceFactor < -1 && getBalanceFactor(root.right) > 0) {
            root.right = rightRotate(root.right);
            return leftRotate(root);
        }
        return root;
    }

    /**
     * private
     * 此时左子树高 - 右子树高 == -2
     * 且刚插入的位置为右子树的右子树的右子树
     *                例如                          本质
     *                10(当前节点 root)      降 root,提 root.right
     *               /  \                    将 10降下来,15提上去
     *              5     15 (newRoot)      ----------------->          15 (newRoot)
     *                  /  \                                            /  \
     *                 12   20                                         10   20
     *                        \                                       /  \    \
     *                        25(该节点就是插入的值)                  5    12   25
     * @param root 为要左旋降低的节点
     * @return 返回新的最后将 newRoot返回
     */
    private Node leftRotate(Node root) {
        // 旋转过程
        Node newRoot = root.right;
        root.right = newRoot.left;
        newRoot.left = root;
        // 更新变换过后节点的高度
        root.height = Math.max(getHeight(root.left), getHeight(root.right)) + 1;
        newRoot.height = Math.max(getHeight(newRoot.left), getHeight(newRoot.right)) + 1;
        return newRoot;
    }

    /**
     * private
     * 此时左子树高 - 右子树高 == 2
     * 且刚插入的位置为左子树的左子树的左子树
     *                例如                        本质
     *                10 (当前节点root)    降 root,提 root.left
     *              /    \                 将 10降下来,5 升上去
     * (newRoot) 5     15                 ------------------>               5(newRoot)
     *           /  \                                                       /  \
     *          4    7                                                     4    10
     *         /                                                         /     /  \
     *        3 (该节点就是插入的值)                                     3     7    15
     * @param root 为要右旋降低的节点
     * @return 最后将 newRoot返回
     */
    private Node rightRotate(Node root) {
        // 旋转过程
        Node newRoot = root.left;
        root.left = newRoot.right;
        newRoot.right = root;
        // 更新变换过后节点的高度
        root.height = Math.max(getHeight(root.left), getHeight(root.right)) + 1;
        newRoot.height = Math.max(getHeight(newRoot.left), getHeight(newRoot.right)) + 1;
        return newRoot;
    }

    /**
     * private
     * @param node
     * @return 返回当前节点的高度
     */
    private int getHeight(Node node) {
        if (node == null)
            return 0;
        return node.height;
    }

    /**
     * private
     * 得到当前节点的平衡因子 (合法值为 -1,0,1)
     * @param node
     * @return 左树的高 - 右树的高 == 平衡因子
     */
    private int getBalanceFactor(Node node) {
        if (node == null)
            return 0;
        return getHeight(node.left) - getHeight(node.right);
    }

    /**
     * private
     * 找到当前节点下的最小值(包括该节点,用于辅助 delete()方法)
     * @param node
     * @return
     */
    private Node findMinNode(Node node) {
        while (node.left != null)// 越向左越小,找到最左边的节点
            node = node.left;
        return node;
    }

    /**
     * 中序遍历
     * 观察是否符合 AVL的性质
     */
    public void inorderTraversal() {
        // 具体实现
        inorderTraversal(this.root);
    }

    /**
     * private
     * @param root
     */
    private void inorderTraversal(Node root) {
        if (root != null) {
            inorderTraversal(root.left);
            System.out.print(root.value + " ");
            inorderTraversal(root.right);
        }
    }

    public static void main(String[] args) {
        AVLTree avl = new AVLTree();
        avl.insert(9);
        avl.insert(5);
        avl.insert(10);
        avl.insert(0);
        avl.insert(6);
        avl.insert(11);
        avl.insert(-1);
        avl.insert(1);
        avl.insert(2);

        System.out.println("中序遍历结果:");
        avl.inorderTraversal();

        System.out.println("\n删除节点 10:");
        avl.delete(10);
        System.out.println("中序遍历结果:");
        avl.inorderTraversal();
    }
}

三、额外话

读者可能还见过另一种带 平衡因子balanceFactor的 AVL代码,

class Node {
    int value;
    int height;
    int balanceFactor;// 平衡因子
    Node left;
    Node right;

    public Node(int value) {
        this.value = value;
        this.height = 1;
        this.balanceFactor = 0;
        this.left = null;
        this.right = null;
    }
}

public class AVLTree {
    private Node root;

    public AVLTree() {
        this.root = null;
    }

    public void insert(int value) {
        this.root = insertNode(this.root, value);
    }

    private Node insertNode(Node root, int value) {
        if (root == null) {
            return new Node(value);
        }
        if (value < root.value) {
            root.left = insertNode(root.left, value);
        } else {
            root.right = insertNode(root.right, value);
        }
        updateHeightAndBalanceFactor(root);
        return balance(root);
    }

    public void delete(int value) {
        this.root = deleteNode(this.root, value);
    }

    private Node deleteNode(Node root, int value) {
        if (root == null) {
            return root;
        }
        if (value < root.value) {
            root.left = deleteNode(root.left, value);
        } else if (value > root.value) {
            root.right = deleteNode(root.right, value);
        } else {
            if (root.left == null || root.right == null) {
                root = (root.left != null) ? root.left : root.right;
            } else {
                Node minNode = findMinNode(root.right);
                root.value = minNode.value;
                root.right = deleteNode(root.right, minNode.value);
            }
        }
        if (root == null) {
            return root;
        }
        updateHeightAndBalanceFactor(root);
        return balance(root);
    }

    private Node balance(Node root) {
        if (root.balanceFactor < -1) {
            if (root.right.balanceFactor > 0) {
                root.right = rotateRight(root.right);
            }
            root = rotateLeft(root);
        } else if (root.balanceFactor > 1) {
            if (root.left.balanceFactor < 0) {
                root.left = rotateLeft(root.left);
            }
            root = rotateRight(root);
        }
        return root;
    }

    private Node rotateLeft(Node root) {
        Node newRoot = root.right;
        root.right = newRoot.left;
        newRoot.left = root;
        updateHeightAndBalanceFactor(root);
        updateHeightAndBalanceFactor(newRoot);
        return newRoot;
    }

    private Node rotateRight(Node root) {
        Node newRoot = root.left;
        root.left = newRoot.right;
        newRoot.right = root;
        updateHeightAndBalanceFactor(root);
        updateHeightAndBalanceFactor(newRoot);
        return newRoot;
    }

    private void updateHeightAndBalanceFactor(Node node) {
        int leftHeight = (node.left != null) ? node.left.height : 0;
        int rightHeight = (node.right != null) ? node.right.height : 0;
        node.height = Math.max(leftHeight, rightHeight) + 1;
        node.balanceFactor = leftHeight - rightHeight;
    }

    private Node findMinNode(Node node) {
        while (node.left != null) {
            node = node.left;
        }
        return node;
    }

    public void inorderTraversal() {
        inorderTraversal(this.root);
    }

    private void inorderTraversal(Node root) {
        if (root != null) {
            inorderTraversal(root.left);
            System.out.print(root.value + " ");
            inorderTraversal(root.right);
        }
    }

    public static void main(String[] args) {
        AVLTree avl = new AVLTree();
        avl.insert(9);
        avl.insert(5);
        avl.insert(10);
        avl.insert(0);
        avl.insert(6);
        avl.insert(11);
        avl.insert(-1);
        avl.insert(1);
        avl.insert(2);

        System.out.println("中序遍历结果:");
        avl.inorderTraversal();

        System.out.println("\n删除节点 10:");
        avl.delete(10);
        System.out.println("中序遍历结果:");
        avl.inorderTraversal();
    }
}

其实与笔者的代码并无太大区别,仅仅是将左右子树的高度相减存入balanceFactor,然后维护balanceFactor的值合法(-1,0,1)来实现 AVL树的自平衡。二者知其一即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值