AVL树

本文详细介绍了AVL树的概念、性质以及平衡因子,阐述了在AVL树中插入和删除节点可能导致的不平衡问题及其解决策略,包括四种旋转类型:LL型、RR型、LR型和RL型。通过示例展示了如何根据关键字序列建立AVL树,并提供了插入和删除操作的Java代码实现,强调了删除操作可能需要多次平衡化处理以保持树的平衡状态。
摘要由CSDN通过智能技术生成

AVL树的性质

AVL树(Balanced Binary Tree or Height-Balanced Tree)
AVL树或者是空二叉树,或者是具有如下性质的BST:

  • 根结点的左、右子树高度之差的绝对值不超过1
  • 且根结点左子树和右子树仍然是AVL树。

结点的平衡因子BF(Balanced Factor)

  • 一个结点的左子树与右子树的高度之差。
  • AVL树中的任意结点的BF只可能是-1,0和1。
  • AVL树的ASL可保持在O(log2n)
    在这里插入图片描述

在AVL树在结点高度上采用相对平衡的策略,使其平均性能接近于BST的最好情况的性能。
因此,对于包含n个结点的AVL树,其最坏情况下的查找、插入和删除操作时间复杂度均为O(log n)

AVL树的平衡化处理

向AVL树插入结点可能造成不平衡,此时要调整树的结构,使之重新达到平衡

在一棵AVL树上插入结点可能会破坏树的平衡性,需要平衡化处理恢复平衡,且保持BST的结构性质。 若用Y表示新插入的结点,A表示离新插入结点Y最近的,且平衡因子变为±2的祖先结点。

可以用4种旋转进行平衡化处理:

  • LL型:新结点Y 被插入到 A 的左子树的左子树上(顺)
  • RR型:新结点Y 被插入到 A 的右子树的右子树上(逆)
  • LR型:新结点Y 被插入到 A 的左子树的右子树上(逆、顺)
  • RL型:新结点Y 被插入到 A 的右子树的左子树上(顺、逆)

1.LL型:新结点Y 被插入到 A 的左子树的左子树上(顺)
在这里插入图片描述
2.RR型:新结点Y 被插入到 A 的右子树的右子树上(逆)
在这里插入图片描述
LR型:新结点Y 被插入到 A 的左子树的右子树上(逆,顺)
在这里插入图片描述
RL型:新结点Y 被插入到 A 的右子树的左子树上(顺, 逆)
在这里插入图片描述

平衡调整代码实现:
左旋

  //左旋转方法
    private void leftRotate() {

        //创建新的结点,以当前根结点的值
        Node newNode = new Node(value);
        //把新的结点的左子树设置成当前结点的左子树
        newNode.left = left;
        //把新的结点的右子树设置成带你过去结点的右子树的左子树
        newNode.right = right.left;
        //把当前结点的值替换成右子结点的值
        value = right.value;
        //把当前结点的右子树设置成当前结点右子树的右子树
        right = right.right;
        //把当前结点的左子树(左子结点)设置成新的结点
        left = newNode;


    }

右旋

 //右旋转
    private void rightRotate() {
        Node newNode = new Node(value);
        newNode.right = right;
        newNode.left = left.right;
        value = left.value;
        left = left.left;
        right = newNode;
    }

平衡调整

 //平衡调整
    private void balanced(){
        //当添加或删除完一个结点后,如果: (右子树的高度-左子树的高度) > 1 , 左旋转
        if(rightHeight() - leftHeight() > 1) {
            //如果它的右子树的左子树的高度大于它的右子树的右子树的高度
            if(right != null && right.leftHeight() > right.rightHeight()) {//RL型
                //先对右子结点进行右旋转
                right.rightRotate();
                //然后在对当前结点进行左旋转
                leftRotate(); //左旋转..
            } else {//RR型
                //直接进行左旋转即可
                leftRotate();
            }
            return ; //必须要!!!
        }

        //当添加完一个结点后,如果 (左子树的高度 - 右子树的高度) > 1, 右旋转
        if(leftHeight() - rightHeight() > 1) {
            //如果它的左子树的右子树高度大于它的左子树的高度
            if(left != null && left.rightHeight() > left.leftHeight()) {//LR型
                //先对当前结点的左结点(左子树)->左旋转
                left.leftRotate();
                //再对当前结点进行右旋转
                rightRotate();
            } else {//LL型
                //直接进行右旋转即可
                rightRotate();
            }
        }

    }

AVL树的插入操作与建立

  • 对于一组关键字的输入序列,从空开始不断地插入结点,最后构成AVL树
  • 每插入一个结点后就应判断从该结点到根的路径上有无结点发生不平衡
  • 如有不平衡问题,利用旋转方法进行树的调整,使之平衡化
  • 建AVL树过程是不断插入结点和必要时进行平衡化的过程

示例:
假设 25,27,30,12, 11,18,14,20,15,22 是一关键字序列,并以上述顺序建立AVL树。

在这里插入图片描述
示例:假设25,27,30,12, 11,18,14,20,15,22 是一关键字序列,并以上述顺序建立AVL树。
在这里插入图片描述
在这里插入图片描述
示例:假设25,27,30,12,11,18, 14,20,15,22 是一关键字序列,并以上述顺序建立AVL树。
在这里插入图片描述
示例:假设25,27,30,12,11,18,14,20, 15,22 是一关键字序列,并以上述顺序建立AVL树。
在这里插入图片描述
示例:假设25,27,30,12,11,18,14,20,15, 22 是一关键字序列,并以上述顺序建立AVL树。
在这里插入图片描述
插入节点的代码实现:

 // 添加结点的方法
    // 递归的形式添加结点,注意需要满足二叉排序树的要求
    public void add(Node node) {
        if (node == null) {
            return;
        }

        // 判断传入的结点的值,和当前子树的根结点的值关系
        if (node.value < this.value) {
            // 如果当前结点左子结点为null
            if (this.left == null) {
                this.left = node;
            } else {
                // 递归的向左子树添加
                this.left.add(node);
            }
        } else { // 添加的结点的值大于 当前结点的值
            if (this.right == null) {
                this.right = node;
            } else {
                // 递归的向右子树添加
                this.right.add(node);
            }
        }
        //添加节点之后做平衡调整
        balanced();
    }

AVL树的删除操作

删除与插入操作是对称的(镜像,互逆的):

  • 删除右子树结点导致失衡时,相当于在左子树插入导致失衡,即LL或LR;
  • 删除左子树结点导致失衡时,相当于在右子树插入导致失衡,即RR或RL;

删除操作可能需要多次平衡化处理

  • 因为平衡化不会增加子树的高度,但可能会减少子树的高度。
  • 在有可能使树增高的插入操作中,一次平衡化能抵消掉树增高;
  • 而在有可能使树减低的删除操作中,平衡化可能会带来祖先结点的不平衡。
  • 因此,删除操作可能需要多次平衡化处理。

示例:删除15,2次旋转到根
在这里插入图片描述
AVL树的节点删除原则与BST树(跳转)的原则相同,只需在BST树的基础上做一些修改即可,即在删除节点之后判断是否需要平衡调节即可

删除AVL树的节点代码实现:

 // 编写方法:
    // 1. 返回的 以node 为根结点的二叉排序树的最小结点的值
    // 2. 删除node 为根结点的二叉排序树的最小结点
    /**
     *
     * @param node
     *            传入的结点(当做二叉排序树的根结点)
     * @return 返回的 以node 为根结点的二叉排序树的最小结点的值
     */
    public int delRightTreeMin(Node node) {
        Node target = node;
        // 循环的查找左子节点,就会找到最小值
        while (target.left != null) {
            target = target.left;
        }
        // 这时 target就指向了最小结点
        // 删除最小结点
        del(target.value);
        return target.value;
    }

    //删除节点
    public void del(int value){

        //找不到待删除的节点
        if(this.left ==null &&this.right ==null){
            System.out.println("未找到待删除节点");
            return;
        }
        // 如果当前结点就是要删除的结点的父结点,或者当前节点就是待删除节点 root
        if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value) || this.value ==value) {

            Node targetNode;//待删除的目标节点
            Node parent ;
            if(this.value ==value){//当待删除的节点没有父节点,即删除的是root节点,
                parent = null;
                targetNode =this;
            }else {
                parent = this;
                targetNode = parent.left != null && parent.left.value == value ? parent.left : parent.right;
            }

            // 如果要删除的结点是叶子结点
            if (targetNode.left == null && targetNode.right == null) {
                // 判断targetNode 是父结点的左子结点,还是右子结点
                if (parent.left != null && parent.left.value == value) { // 是左子结点
                    parent.left = null;
                } else if (parent.right != null && parent.right.value == value) {// 是由子结点
                    parent.right = null;
                }
            } else if (targetNode.left != null && targetNode.right != null) { // 删除有两颗子树的节点
                int minVal = delRightTreeMin(targetNode.right);
                targetNode.value = minVal;

            } else { // 删除只有一颗子树的结点
                // 如果要删除的结点有左子结点
                if (targetNode.left != null) {
                    if (parent != null) {
                        // 如果 targetNode 是 parent 的左子结点
                        if (parent.left.value == value) {
                            parent.left = targetNode.left;
                        } else { // targetNode 是 parent 的右子结点
                            parent.right = targetNode.left;
                        }
                    }
                } else { // 如果要删除的结点有右子结点
                    if (parent != null) {
                        // 如果 targetNode 是 parent 的左子结点
                        if (parent.left.value == value) {
                            parent.left = targetNode.right;
                        } else { // 如果 targetNode 是 parent 的右子结点
                            parent.right = targetNode.right;
                        }
                    }
                }

            }

        }
        // 如果查找的值小于当前结点的值, 并且当前结点的左子结点不为空
        if (value < this.value && this.left != null) {
            this.left.del(value); // 向左子树递归查找
        }  // 如果查找的值大于当前结点的值, 并且当前结点的左子结点不为空
        if (value >= this.value && this.right != null) {
            this.right.del(value); // 向右子树递归查找
        }
        //删除之后做平衡调整,递归返回时依次从下网上调整
        balanced();

    }

完整代码(java)

public class AVLTreeDemo {

    public static void main(String[] args) {

        int[] arr = {25,27,30,12,11,18,14,20,15,22 };
        //创建一个 AVLTree对象
        AVLTree avlTree = new AVLTree();
        //添加结点
        for(int i=0; i < arr.length; i++) {
            avlTree.add(new Node(arr[i]));
        }
        //遍历
        System.out.println("中序遍历");
        avlTree.infixOrder();
        System.out.println("在平衡处理~~");
        System.out.println("树的高度=" + avlTree.getRoot().height()); //3
        System.out.println("树的左子树高度=" + avlTree.getRoot().leftHeight()); // 2
        System.out.println("树的右子树高度=" + avlTree.getRoot().rightHeight()); // 2
        System.out.println("当前的根结点=" + avlTree.getRoot());//8
        avlTree.delNode(100);
        //遍历
        System.out.println("中序遍历");
        avlTree.infixOrder();
        System.out.println("在平衡处理~~");
        System.out.println("树的高度=" + avlTree.getRoot().height()); //3
        System.out.println("树的左子树高度=" + avlTree.getRoot().leftHeight()); // 2
        System.out.println("树的右子树高度=" + avlTree.getRoot().rightHeight()); // 2
        System.out.println("当前的根结点=" + avlTree.getRoot());//8
    }
}

// 创建AVLTree
class AVLTree {
    private Node root;
    public Node getRoot() { return root; }
    // 删除结点
    public void delNode(int value) {
        if (root == null) {
            return;
        }
        //如果删除的是 root节点,且root节点只有一个子节点
       if(root.left==null && root.value ==value){
           root = root.right;
           return;
       }
       if(root.right==null && root.value ==value){
           root = root.left;
           return;
       }
        root.del(value);
    }
    // 添加结点的方法
    public void add(Node node) {
        if (root == null) {
            root = node;// 如果root为空则直接让root指向node
        } else {
            root.add(node);
        }
    }

    // 中序遍历
    public void infixOrder() {
        if (root != null) {
            root.infixOrder();
        } else {
            System.out.println("二叉排序树为空,不能遍历");
        }
    }
}

// 创建Node结点
class Node {
    int value;
    Node left;
    Node right;
    public Node(int value) { this.value = value; }
    @Override
    public String toString() { return "Node [value=" + value + "]"; }
    // 返回左子树的高度
    public int leftHeight() {
        if (left == null) { return 0; }
        return left.height();
    }

    // 返回右子树的高度
    public int rightHeight() {
        if (right == null) { return 0; }
        return right.height();
    }

    // 返回 以该结点为根结点的树的高度
    public int height() {
        return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;
    }

    //左旋转方法
    private void leftRotate() {

        //创建新的结点,以当前根结点的值
        Node newNode = new Node(value);
        //把新的结点的左子树设置成当前结点的左子树
        newNode.left = left;
        //把新的结点的右子树设置成带你过去结点的右子树的左子树
        newNode.right = right.left;
        //把当前结点的值替换成右子结点的值
        value = right.value;
        //把当前结点的右子树设置成当前结点右子树的右子树
        right = right.right;
        //把当前结点的左子树(左子结点)设置成新的结点
        left = newNode;


    }

    //右旋转
    private void rightRotate() {
        Node newNode = new Node(value);
        newNode.right = right;
        newNode.left = left.right;
        value = left.value;
        left = left.left;
        right = newNode;
    }

    //平衡调整
    private void balanced(){
        //当添加或删除完一个结点后,如果: (右子树的高度-左子树的高度) > 1 , 左旋转
        if(rightHeight() - leftHeight() > 1) {
            //如果它的右子树的左子树的高度大于它的右子树的右子树的高度
            if(right != null && right.leftHeight() > right.rightHeight()) {//RL型
                //先对右子结点进行右旋转
                right.rightRotate();
                //然后在对当前结点进行左旋转
                leftRotate(); //左旋转..
            } else {//RR型
                //直接进行左旋转即可
                leftRotate();
            }
            return ; //必须要!!!
        }

        //当添加完一个结点后,如果 (左子树的高度 - 右子树的高度) > 1, 右旋转
        if(leftHeight() - rightHeight() > 1) {
            //如果它的左子树的右子树高度大于它的左子树的高度
            if(left != null && left.rightHeight() > left.leftHeight()) {//LR型
                //先对当前结点的左结点(左子树)->左旋转
                left.leftRotate();
                //再对当前结点进行右旋转
                rightRotate();
            } else {//LL型
                //直接进行右旋转即可
                rightRotate();
            }
        }

    }

    // 添加结点的方法
    // 递归的形式添加结点,注意需要满足二叉排序树的要求
    public void add(Node node) {
        if (node == null) {
            return;
        }

        // 判断传入的结点的值,和当前子树的根结点的值关系
        if (node.value < this.value) {
            // 如果当前结点左子结点为null
            if (this.left == null) {
                this.left = node;
            } else {
                // 递归的向左子树添加
                this.left.add(node);
            }
        } else { // 添加的结点的值大于 当前结点的值
            if (this.right == null) {
                this.right = node;
            } else {
                // 递归的向右子树添加
                this.right.add(node);
            }
        }
        //添加节点之后做平衡调整
        balanced();
    }

    // 编写方法:
    // 1. 返回的 以node 为根结点的二叉排序树的最小结点的值
    // 2. 删除node 为根结点的二叉排序树的最小结点
    /**
     *
     * @param node
     *            传入的结点(当做二叉排序树的根结点)
     * @return 返回的 以node 为根结点的二叉排序树的最小结点的值
     */
    public int delRightTreeMin(Node node) {
        Node target = node;
        // 循环的查找左子节点,就会找到最小值
        while (target.left != null) {
            target = target.left;
        }
        // 这时 target就指向了最小结点
        // 删除最小结点
        del(target.value);
        return target.value;
    }

    //删除节点
    public void del(int value){

        //找不到待删除的节点
        if(this.left ==null &&this.right ==null){
            System.out.println("未找到待删除节点");
            return;
        }
        // 如果当前结点就是要删除的结点的父结点,或者当前节点就是待删除节点 root
        if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value) || this.value ==value) {

            Node targetNode;//待删除的目标节点
            Node parent ;
            if(this.value ==value){//当待删除的节点没有父节点,即删除的是root节点,
                parent = null;
                targetNode =this;
            }else {
                parent = this;
                targetNode = parent.left != null && parent.left.value == value ? parent.left : parent.right;
            }

            // 如果要删除的结点是叶子结点
            if (targetNode.left == null && targetNode.right == null) {
                // 判断targetNode 是父结点的左子结点,还是右子结点
                if (parent.left != null && parent.left.value == value) { // 是左子结点
                    parent.left = null;
                } else if (parent.right != null && parent.right.value == value) {// 是由子结点
                    parent.right = null;
                }
            } else if (targetNode.left != null && targetNode.right != null) { // 删除有两颗子树的节点
                int minVal = delRightTreeMin(targetNode.right);
                targetNode.value = minVal;

            } else { // 删除只有一颗子树的结点
                // 如果要删除的结点有左子结点
                if (targetNode.left != null) {
                    if (parent != null) {
                        // 如果 targetNode 是 parent 的左子结点
                        if (parent.left.value == value) {
                            parent.left = targetNode.left;
                        } else { // targetNode 是 parent 的右子结点
                            parent.right = targetNode.left;
                        }
                    }
                } else { // 如果要删除的结点有右子结点
                    if (parent != null) {
                        // 如果 targetNode 是 parent 的左子结点
                        if (parent.left.value == value) {
                            parent.left = targetNode.right;
                        } else { // 如果 targetNode 是 parent 的右子结点
                            parent.right = targetNode.right;
                        }
                    }
                }

            }

        }
        // 如果查找的值小于当前结点的值, 并且当前结点的左子结点不为空
        if (value < this.value && this.left != null) {
            this.left.del(value); // 向左子树递归查找
        }  // 如果查找的值大于当前结点的值, 并且当前结点的左子结点不为空
        if (value >= this.value && this.right != null) {
            this.right.del(value); // 向右子树递归查找
        }
        //删除之后做平衡调整,递归返回时依次从下网上调整
        balanced();

    }

    // 中序遍历
    public void infixOrder() {
        if (this.left != null) {
            this.left.infixOrder();
        }
        System.out.println(this);
        if (this.right != null) {
            this.right.infixOrder();
        }
    }

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值