平衡二叉树的插入与删除

定义

AVL树是带有平衡条件的二叉查找树。它要求在AVL树中任何节点的两个子树的高度(高度是指节点到一片树叶的最长路径的长) 最大差别为1,如下图所示:

《平衡二叉树的插入与删除》

为什么有AVL树

大多数BST操作,例如查找,找最大,最小值,插入,删除等操作,基本上消耗O(h)时间,h是BST的高度。对于倾斜的二叉树,这些操作的成本可能会变成O(n)。

如果我们在每次插入和删除之后确保树的高度保持O(logn),那么我们就可以保证所有这些操作的O(logn)的上界。而AVL树的高度总是O(logn),其中n是树中节点的数量。

所以AVL树很好的解决了二叉搜索树退化成链表的问题。把插入,查找,删除的时间复杂度的最好情况和最坏情况都维持在O(logn)

插入操作

当我们执行插入一个节点时,很可能会破坏AVL树的平衡特性,所以我们需要调整AVL树的结构,使其重新平衡,而调整的方式称之为旋转

这里我们针对父节点的位置分为左-左左-右右-右右-左这4类情况分析,而对左-左右-右情况进行单旋转,也就是一次旋转,对左-右右-左情况进行双旋转,两次旋转,最终恢复其平衡特性。

1.左-左情况

《平衡二叉树的插入与删除》

2.左-右情况

《平衡二叉树的插入与删除》

3.右-右情况

《平衡二叉树的插入与删除》

4.右-左情况

《平衡二叉树的插入与删除》

代码实现

package com.pjmike.tree.AVL;

/** * AVL树 * * @author pjmike * @create 2018-08-09 17:57 */
public class AVLTree {
    private Node root;

    /** * 计算AVL节点的高度的方法 * * @param node * @return */
    private int height(Node node) {
        //如果为空,返回height为0
        return node == null ? 0 : node.height;
    }

    /** * 计算两个的最大值 * * @param a * @param b * @return */
    private int max(int a, int b) {
        return a > b ? a : b;
    }
    /** * 右旋转 * * @param y * @return */
    Node rightRotate(Node y) {
        Node x = y.left;
        Node T1 = x.right;
        x.right = y;
        y.left = T1;
        //更新高度
        y.height = max(height(y.left), height(y.right)) + 1;
        x.height = max(height(x.left), height(x.right)) + 1;
        return x;
    }

    /** * 左旋转 * * @param x * @return */
    Node leftRotate(Node x) {

        Node y = x.right;
        Node T2 = y.left;
        y.left = x;
        x.right = T2;
        //更新高度
        x.height = max(height(x.left), height(x.right)) + 1;
        y.height = max(height(y.left), height(y.right)) + 1;
        return y;
    }

    /** * 获取平衡因子 * * @param node * @return */
    int getBalance(Node node) {
        return node == null ? 0 : (height(node.left) - height(node.right));
    }
    /** * 插入操作 * * @param node * @param val * @return */
    Node insert(Node node, int val) {
        if (node == null) {
            return new Node(val);
        }
        if (val < node.val) {
            node.left = insert(node.left, val);
        } else if (val > node.val) {
            node.right = insert(node.right, val);
        } else {
            return node;
        }
        //更新节点高度
        node.height = 1 + max(height(node.left), height(node.right));
        //这是插入完毕后的
        int balance = getBalance(node);
        if (balance > 1 && val < node.left.val) {
            //右旋
            return rightRotate(node);
        }
        if (balance < -1 && val > node.right.val) {
            //左旋
            return leftRotate(node);
        }
        if (balance > 1 && val > node.left.val) {
            //先左旋,再右旋
            node.left = leftRotate(node.left);
            return rightRotate(node);
        }
        if (balance < -1 && val < node.right.val) {
            //先右旋再左旋
            node.right = rightRotate(node.right);
            return leftRotate(node);
        }
        return node;
    }

    public static void main(String[] args) {
        AVLTree avlTree = new AVLTree();
        //创造AVL树
        avlTree.root = avlTree.insert(avlTree.root, 10);
        avlTree.root = avlTree.insert(avlTree.root, 20);
        avlTree.root = avlTree.insert(avlTree.root, 30);
        avlTree.root = avlTree.insert(avlTree.root, 40);
        avlTree.root = avlTree.insert(avlTree.root, 50);
        avlTree.root = avlTree.insert(avlTree.root, 23);
        avlTree.Preorder(avlTree.root);
    }

    void Preorder(Node node) {
        if (node != null) {
            System.out.println(node.val+" ");
            Preorder(node.left);
            Preorder(node.right);
        }
    }
}

class Node {
    int val;
    Node left;
    Node right;
    /** * 节点高度,高度是指节点到一片树叶的最长路径的长 */
    int height;

    public Node(int val) {
        this.val = val;
        height = 1;
    }

}

删除操作

对于二叉查找树,我们都知道它的删除要分三种情况:
– 删除的节点为叶子节点
– 删除的节点左子树或右子树有一个为空
– 删除的节点有两个子树

AVL本身是带有平衡性质的二叉搜索树,所以它的删除策略是与二叉查找树相似的,只不过删除节点后可能会造成树失去平衡性,所以需要做平衡处理

代码实现

  Node deleteNode(Node root, int val) {
        if (root == null) {
            return root;
        }
        if (val < root.val) {
            root.left = deleteNode(root.left, val);
        } else if (val > root.val) {
            root.right = deleteNode(root.right, val);
        } else {
            //删除节点有两个孩子
            if (root.left != null && root.right != null) {
                root.val = findMin(root.right);
                root.right = deleteNode(root.right, root.val);
            } else {
            //删除节点只有一个孩子或者没有孩子
                root = (root.left != null) ? root.left : root.right;
            }
        }
        //以下操作是为了恢复AVL树的平衡性
        if (root == null) {
            return root;
        }
        root.height = max(height(root.left), height(root.right)) + 1;
        int balance = getBalance(root);
        //左-左情况,这里使用>=而不是>就是为了保证这些情形下使用的是单旋转而不是双旋转
        if (balance > 1 && getBalance(root.left) >= 0) {
            return rightRotate(root);
        }
        //左-右情况
        if (balance > 1 && getBalance(root.left) < 0)
        {
            root.left = leftRotate(root.left);
            return rightRotate(root);
        }

        //右-右情况
        if (balance < -1 && getBalance(root.right) <= 0) {
            return leftRotate(root);
        }
        //右-左情况
        if (balance < -1 && getBalance(root.right) > 0)
        {
            root.right = rightRotate(root.right);
            return leftRotate(root);
        }
        return root;
    }

    private int findMin(Node root) {
        if (root.left == null) {
            return root.val;
        } else {
            return findMin(root.left);
        }
    }

这里的删除后的平衡操作实际上与插入后的平衡操作是不一样的,删除可能造成树的一边比另一边浅的情况,还可能造成整个二叉树中有两个子树一样深的情况。

小结

显然,AVL树的优势在于插入,删除,查找操作的平均时间复杂度都为O(logn),但是它的缺陷是为了保持AVL树的平衡性质,动态插入和删除的代价也是很大的

参考资料

    原文作者:平衡二叉树
    原文地址: https://blog.csdn.net/pjmike233/article/details/81545136
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞