Java——AVL树

平衡二叉树

在之前的blog中讲到,平衡二叉树是一棵树,任意一个节点的左树的所有节点都小于这个节点,右树的所有节点都大于这个节点
因此,可以利用这个性质来中序遍历,就可以得到一个有序的序列,而如果我们要查找某一个值所在的节点,如果这个平衡二叉树的每个子树的左右子树高度接近,那么就会和二分查找一样,每经过一个节点就会直接淘汰一半的值,因此时间复杂度就是log以2为底的n
但是,由于并不是所有平衡二叉树都是左右子树高度接近,因此,假如出现了一棵平衡二叉树所有节点都是只有右子树,那么查找速度就会退化为链表
为了避免这种情况发生,AVL树应运而生

AVL树

是一颗平衡二叉树,并且满足左右子树的高度差不超过1
这样的话,我们的查找效率就得到了保证

平衡因子

就是用来计算二叉树左右子树的高度差的
平衡因子 = 右子树高度 - 左子树高度

节点定义

在AVL树中,不仅要定义左右孩子,还要定义平衡因子,和父亲树

static class TreeNode {
        public int val;
        public int bf;// 平衡因子
        public TreeNode left;
        public TreeNode right;
        public TreeNode parent;

        public TreeNode(int val){
            this.val = val;
        }
    }

插入操作

由于AVL树需要时刻满足左右子树的高度差不大于1,那么在插入一个元素时就需要不断的调整

首先按照平衡二叉树的方法进行插入
先判断根节点是否为空,为空则直接插入
然后定义一个cur和parent,cur先为root,cur一直向下遍历,如果val小于cur.val,cur就变成cur.left,大于的话就变成cur.right,如果等于,就说明已经有这个节点了,return false,在cur遍历的过程中,使parent始终为cur的上一个节点,使得当cur变成空时,能够记录上一个节点的位置
然后判断val和parent.val的大小关系,插到对应的位置上

TreeNode node = new TreeNode(val);
        if(root == null){
            root = node;
            return true;
        }
        TreeNode parent = null;
        TreeNode cur = root;
        while(cur != null){
            if (cur.val < val){
                parent = cur;
                cur = cur.right;
            } else if (cur.val > val){
                parent = cur;
                cur = cur.left;
            } else {
                return false;
            }
        }
        if(parent.val < val){
            parent.right = node;
        } else {
            parent.left = node;
        }
        node.parent = parent;
        cur = node;

接下来,让cur重新变成插入的节点,parent变成插入节点的父节点,然后就应该调整树的结构,修改平衡因子,分为以下几种情况

如果插入节点是右子树,那么说明父亲节点的平衡因子应该++,否则就应该–

如果parent的bf已经是0了,那么上面也就不需要继续调整了,可以直接break

如果parent的bf是1或者-1,说明其子树已经平衡了,但是上面的结构不一定平衡,因此需要向上迭代,让cur为parent,parent为parent的parent

如果parent的平衡因子是2,cur的平衡因子是1,也就是下图的情况,那么需要左单旋(后面讲)
在这里插入图片描述

如果parent的平衡因子是2,cur的平衡因子是-1,也就是下图的情况,那么需要右左双旋(后面讲)
在这里插入图片描述

如果parent的平衡因子是-2,cur的平衡因子是-1,也就是下图的情况,那么需要右单旋(后面讲)
在这里插入图片描述

如果parent的平衡因子是-2,cur的平衡因子是1,也就是下图的情况,那么需要左右双旋,也就是先左旋,再右旋(后面讲)
在这里插入图片描述

//调节平衡因子
        while(parent != null){
            //判断cur是parent的左还是右,决定parent的平衡因子变换
            if(cur == parent.right){
                parent.bf++;
            } else {
                parent.bf--;
            }

            if(parent.bf == 0){
                //整个树已经平衡
                break;
            } else if (parent.bf == 1 || parent.bf == -1){
                //子树平衡了,继续向上判断平衡因子
                cur = parent;
                parent = cur.parent;
            }else {
                if(parent.bf == 2){
                    //右树高,降低右树的高度
                    if(cur.bf == 1){
                        //左单旋
                        rotateLeft(parent);
                    } else {
                        //cur.bf == -1
                        rotateRL(parent);
                    }
                } else {
                    //左树高,降低左树的高度
                    //parent.bf = -2
                    if(cur.bf == -1){
                        //右单旋
                        rotateRight(parent);
                    } else {
                        //cur.bf == 1
                        rotateLR(parent);
                    }
                }

                //调整后就平衡了
                break;
            }
        }
        return true;

而对于循环条件,此循环是为了从下到上调节所有与插入节点有关的节点的平衡因子,因此需要向上迭代,最终的终止条件就是cur迭代到根,parent迭代到空

左单旋

定义parent的右孩子为subR,subR的左孩子为subRL,并依据图上关系重新组织其之间的结构。判断parent是不是根节点,如果为根就让subR为根,如果不为根,就让parent的父节点的孩子指针指向subR

然后更改平衡因子,按照图上关系,subR的平衡因子应该改为0,parent的平衡因子应该改成0(subRR的平衡因子在第一次迭代的时候就改过了)
在这里插入图片描述

private void rotateLeft(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRl = subR.left;

        parent.right = subRl;
        subR.left = parent;
        if(subRl != null){
            subRl.parent = parent;
        }

        TreeNode pParent = parent.parent;

        parent.parent = subR;

        if(root == parent){
            root = subR;
            root.parent = null;
        } else {
            if (pParent.left == parent){
                pParent.left = subR;
            } else {
                pParent.right = subR;
            }
            subR.parent = pParent;
        }

        parent.bf = 0;
        subR.bf = 0;
    }

右单旋

定义parent的左孩子为subL,subL的右孩子为subLR,并依据图上关系重新组织其之间的结构。判断parent是不是根节点,如果为根就让subL为根,如果不为根,就让parent的父节点的孩子指针指向subL

然后更改平衡因子,按照图上关系,subL的平衡因子应该改为0,parent的平衡因子应该改成0(subLL的平衡因子在第一次迭代的时候就改过了)
在这里插入图片描述

private void rotateRight(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;

        parent.left = subLR;
        subL.right = parent;
        if(subLR != null) {
            subLR.parent = parent;
        }

        TreeNode pParent = parent.parent;

        parent.parent = subL;

        //检查是否parent为根节点
        if(parent == root){
            //使subL为根节点
            root = subL;
            root.parent = null;
        } else {
            //使得parent的原parent节点的孩子为subL
            if (pParent.left == parent) {
                pParent.left = subL;
            } else {
                pParent.right = subL;
            }
            subL.parent = pParent;
        }

        //修改平衡因子
        parent.bf = 0;
        subL.bf = 0;
    }

右左双旋

定义parent的右孩子为subR,subR的左孩子为subRL,并记录subRL的平衡因子bf

右左双旋有两种情况:

情况一:
新节点插到subRL的右孩子处,也就是bf为1
在这里插入图片描述
情况二:
新节点插到subRL的左孩子处,也就是bf为-1
在这里插入图片描述
这两种情况的修改树结构的流程是一样的,但是在修改平衡因子时有所不同

先修改树的结构:
先让parent的右孩子进行右旋转,然后让parent进行左旋转

修改平衡因子:
判断bf为-1还是1还是0
如果是1,那么就是第一张图的情况,所以parent的平衡因子会修改成-1,subRL和subR的平衡因子会修改为0

如果是-1,那么就是第二张图的情况,所以subR的平衡因子会修改为1,subRL和parent的平衡因子是0

如果是0,就相当于没插新孩子,那么就不用修正平衡因子了

private void rotateRL(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;
        int bf = subRL.bf;

        rotateRight(parent.right);
        rotateLeft(parent);

        //修改负载因子,依据subRL的负载因子的大小
        if(bf == -1){
            subR.bf = 0;
            subRL.bf = 1;
            parent.bf = 0;
        } else if (bf == 1){
            //bf == 1
            subR.bf = 0;
            subRL.bf = 0;
            parent.bf = -1;
        }
        //bf为0不需要修改平衡因子
    }

左右双旋

和右左双旋的思路大致相同

定义parent的左孩子为subL,subL的右孩子为subLR,并记录subLR的平衡因子bf

右左双旋有两种情况:
情况一:
新节点插到subRL的右孩子处,也就是bf为1
在这里插入图片描述

情况二:
新节点插到subRL的左孩子处,也就是bf为-1
在这里插入图片描述
这两种情况的修改树结构的流程是一样的,但是在修改平衡因子时有所不同

先修改树的结构:
先让parent的右孩子进行右旋转,然后让parent进行左旋转

修改平衡因子:
判断bf为-1还是1还是0
如果是1,那么就是第一张图的情况,所以subL的平衡因子会修改成-1,subLR和parent的平衡因子会修改为0

如果是-1,那么就是第二张图的情况,所以parent的平衡因子会修改为1,subLR和subL的平衡因子是0

如果是0,就相当于没插新孩子,那么就不用修正平衡因子了

private void rotateLR(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        int bf = subLR.bf;

        rotateLeft(parent.left);
        rotateRight(parent);

        //修改负载因子,依据subLR的负载因子的大小
        if(bf == -1){
            subL.bf = 0;
            subLR.bf = 0;
            parent.bf = 1;
        } else if (bf == 1){
            subL.bf = -1;
            subLR.bf = 0;
            parent.bf = 0;
        }
        //bf为0不需要修改平衡因子
    }

到这里,插入代码就写完了

删除节点

删除节点和平衡二叉树是一样的,先找到一个替罪的,然后交换位置后再删除,唯一设计到的就是调节平衡因子,具体方法和添加节点是一样的,这里就不详细讲解了

验证AVL树

avl树不仅要通过平衡因子来判定是否正确,还需要判断其中序遍历是否为有序的,判断其是否为平衡二叉树(因为我们的平衡因子有可能算错了),因此下面的代码就比较简单了

中序遍历

public void inorder(TreeNode root){
        if(root == null){
            return;
        }
        inorder(root.left);
        System.out.println(root.val);
        inorder(root.right);
    }

判断一个节点的高度

private int height(TreeNode root){
        if(root == null){
            return 0;
        }
        int left = height(root.left);
        int right = height(root.right);
        return Math.max(left,right) + 1;
    }

判断是否为平衡二叉树

public boolean isBalance(TreeNode root){
    if (root == null){
        return true;
    }
    int leftHeight = height(root.left);
    int rightHeight = height(root.right);

    if(rightHeight - leftHeight != root.bf){
        System.out.println("次节点:" + root.val + "平衡因子异常!");
        return false;
    }

    return Math.abs(leftHeight - rightHeight) <= 1
            && isBalance(root.left)
            && isBalance(root.right);
}

如果平衡因子和高度不一致,那么可以直接终止判定,打印一个错误

验证代码

package AVLTree;

public class Test {
    public static void main(String[] args) {
        //int[] array = {16,3,7,11,9,26,18,14,15};
        int[] array = {4,2,6,1,3,5,15,7,16,14};
        AVLTree avlTree = new AVLTree();
        for (int i = 0; i < array.length; i++) {
            avlTree.insert(array[i]);
        }

        System.out.println(avlTree.isBalance(avlTree.root));
        avlTree.inorder(avlTree.root);
    }
}

通过上述代码,可以看到,我们的avl树的插入是正确的

完整插入和验证代码

package AVLTree;

import apple.laf.JRSUIUtils;

public class AVLTree {
    static class TreeNode {
        public int val;
        public int bf;// 平衡因子
        public TreeNode left;
        public TreeNode right;
        public TreeNode parent;

        public TreeNode(int val){
            this.val = val;
        }
    }

    public TreeNode root;

    public boolean insert(int val){
        TreeNode node = new TreeNode(val);
        if(root == null){
            root = node;
            return true;
        }
        TreeNode parent = null;
        TreeNode cur = root;
        while(cur != null){
            if (cur.val < val){
                parent = cur;
                cur = cur.right;
            } else if (cur.val > val){
                parent = cur;
                cur = cur.left;
            } else {
                return false;
            }
        }
        if(parent.val < val){
            parent.right = node;
        } else {
            parent.left = node;
        }
        node.parent = parent;
        cur = node;

        //调节平衡因子
        while(parent != null){
            //判断cur是parent的左还是右,决定parent的平衡因子变换
            if(cur == parent.right){
                parent.bf++;
            } else {
                parent.bf--;
            }

            if(parent.bf == 0){
                //整个树已经平衡
                break;
            } else if (parent.bf == 1 || parent.bf == -1){
                //子树平衡了,继续向上判断平衡因子
                cur = parent;
                parent = cur.parent;
            }else {
                if(parent.bf == 2){
                    //右树高,降低右树的高度
                    if(cur.bf == 1){
                        //左单旋
                        rotateLeft(parent);
                    } else {
                        //cur.bf == -1
                        rotateRL(parent);
                    }
                } else {
                    //左树高,降低左树的高度
                    //parent.bf = -2
                    if(cur.bf == -1){
                        //右单旋
                        rotateRight(parent);
                    } else {
                        //cur.bf == 1
                        rotateLR(parent);
                    }
                }

                //调整后就平衡了
                break;
            }
        }
        return true;
    }

    /**
     * 先右旋,再左旋
     * @param parent
     */
    private void rotateRL(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;
        int bf = subRL.bf;

        rotateRight(parent.right);
        rotateLeft(parent);

        //修改负载因子,依据subRL的负载因子的大小
        if(bf == -1){
            subR.bf = 1;
            subRL.bf = 0;
            parent.bf = 0;
        } else if (bf == 1){
            //bf == 1
            subR.bf = 0;
            subRL.bf = 0;
            parent.bf = -1;
        }
        //bf为0不需要修改平衡因子
    }

    /**
     * 先左旋,后右旋
     * @param parent
     */
    private void rotateLR(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        int bf = subLR.bf;

        rotateLeft(parent.left);
        rotateRight(parent);

        //修改负载因子,依据subLR的负载因子的大小
        if(bf == -1){
            subL.bf = 0;
            subLR.bf = 0;
            parent.bf = 1;
        } else if (bf == 1){
            subL.bf = -1;
            subLR.bf = 0;
            parent.bf = 0;
        }
        //bf为0不需要修改平衡因子
    }

    /**
     * 左单旋
     * @param parent
     */
    private void rotateLeft(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRl = subR.left;

        parent.right = subRl;
        subR.left = parent;
        if(subRl != null){
            subRl.parent = parent;
        }

        TreeNode pParent = parent.parent;

        parent.parent = subR;

        if(root == parent){
            root = subR;
            root.parent = null;
        } else {
            if (pParent.left == parent){
                pParent.left = subR;
            } else {
                pParent.right = subR;
            }
            subR.parent = pParent;
        }

        parent.bf = 0;
        subR.bf = 0;
    }

    /**
     * 右单旋
     * @param parent
     */
    private void rotateRight(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;

        parent.left = subLR;
        subL.right = parent;
        if(subLR != null) {
            subLR.parent = parent;
        }

        TreeNode pParent = parent.parent;

        parent.parent = subL;

        //检查是否parent为根节点
        if(parent == root){
            //使subL为根节点
            root = subL;
            root.parent = null;
        } else {
            //使得parent的原parent节点的孩子为subL
            if (pParent.left == parent) {
                pParent.left = subL;
            } else {
                pParent.right = subL;
            }
            subL.parent = pParent;
        }

        //修改平衡因子
        parent.bf = 0;
        subL.bf = 0;
    }

    /**
     * 中序遍历
     * @param root
     */
    public void inorder(TreeNode root){
        if(root == null){
            return;
        }
        inorder(root.left);
        System.out.println(root.val);
        inorder(root.right);
    }

    private int height(TreeNode root){
        if(root == null){
            return 0;
        }
        int left = height(root.left);
        int right = height(root.right);
        return Math.max(left,right) + 1;
    }

    /**
     * 判断是不是平衡二叉树
     * @param root
     * @return
     */
    public boolean isBalance(TreeNode root){
        if (root == null){
            return true;
        }
        int leftHeight = height(root.left);
        int rightHeight = height(root.right);

        if(rightHeight - leftHeight != root.bf){
            System.out.println("次节点:" + root.val + "平衡因子异常!");
            return false;
        }

        return Math.abs(leftHeight - rightHeight) <= 1
                && isBalance(root.left)
                && isBalance(root.right);
    }
}

分析

可以看到,我们的avl树的插入过程十分复杂,如果改动一个数据就需要从下到上重新组织数据,因此avl树只适合查找数据,并不适合频繁的插入删除数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值