手撕AVL树

🔥个人主页🔥:鱼骨不是鱼翅-CSDN博客
🌈收录专栏🌈:高阶数据结构_鱼骨不是鱼翅的博客-CSDN博客
🔖 学如逆水行舟,不进则退

目录

一.AVL树的概念

二.平衡因子

三.平衡二叉树怎么调整失衡

1.左旋(RR型):

2.右旋(LL型):

3.左右双旋(LR型):

4.右左双旋(RL型):

5.插入节点有多个祖先失衡

四.平衡二叉树插入操作代码编写(JAVA)

1.创建一个节点类(avlTree类里的成员内部类)

2.创建一个指向根节点的成员变量

3.书写插入方法

3.1实例化节点,判断树是否为空

3.2寻找插入节点的位置

3.3调整平衡

3.4旋转代码

3.4.1左旋代码

3.4.2右旋代码

3.4.3左右双旋代码

3.4.4右左双旋

 五.判断一棵树是否是平衡二叉树

1.中序遍历:

2.判断每个节点左右树高差的绝对值


一.AVL树的概念

AVL树又叫平衡二叉树,他是一种自平衡的二叉搜索树

前提:AVL树得是一颗二叉搜索树

条件:在前提条件下,保证每个节点的左子树和右子树的高度差不超过1

          ((左子树高度-右子树高度)的绝对值<=1)

平衡因子:左子树高度-右子树高的值

AVL树弥补了二叉树搜索树在数据本来就有序时,变成单链表,查询的时间复杂的变为O(n)的缺陷

二.平衡因子

看下面这棵树

上面这棵树它是一棵二叉搜索树,但是不是一棵平衡二叉树

大家可以试着写一下每一个节点的平衡因子:左子树高度-右子树高度

平衡因子:

节点7:2-3=-1;

节点4:1-1=0;

节点1:0-0=0;

节点6:0-0=0;

节点8:0-2=-2;

节点9:0-1=-1;

节点12:0-0=0;

通过观察,我们发现节点8的平衡因子的绝对值为2,大于了1,所以上面这棵树不是一棵平衡二叉树

一棵树要想是平衡二叉树,那么每个节点的平衡因子的取值只能是(-1,0,1)

三.平衡二叉树怎么调整失衡

1.左旋(RR型):

失衡节点的平衡因子为-2,失衡节点右孩子节点的平衡因子为-1

 树2是由数1左旋得到的

当插入节点7时,发现节点3的平衡因子变成了-2,破坏了二叉搜索树的平衡,这时我们通过左旋将其变为树2,维护了二叉搜索树的平衡

左旋:将节点3绕节点4逆时针旋转

下面来看一个复杂一点的左旋

 通过对上面第一个简单左旋,我们发现左旋就是将平衡因子为-2的节点绕其右孩子节点向左旋转或者说是逆时针旋转

然而上面这个左旋,旋转中心是节点8,旋转点是节点6,当旋转点(节点6)绕旋转中心节点(节点8)旋转,节点6旋转作为节点8的左孩子节点时,会和原本节点8的左孩子冲突,这时就需要让节点8的左孩子节点作为节点6的右孩子节点就行

记忆口诀:冲突的左孩作右孩

解释:冲突的左孩是指旋转中心的左孩子,做右孩是让旋转中心的左孩子作为旋转点的右孩子,旋转点作为旋转中心的左孩子

2.右旋(LL型):

失衡节点的平衡因子为2,失衡节点右孩子节点的平衡因子为1

 右旋于左旋类似

上图中的树2是由树1右旋得到的

有了左旋的经验,我们很容易发现

节点12的平衡因子是2,破坏了AVL树的平衡,所以我们需要对这棵树进行调整

旋转中心:节点9

旋转点:节点12

右旋:旋转点绕旋转中心向右旋转(顺时针旋转)

节点12绕节点9顺时针旋转得到树2

下面来看一个复杂点的右旋,原理和左旋复杂一点的类似

 通过对上面第一个简单右旋,我们发现右旋就是将平衡因子为2的节点绕其右孩子节点向右旋转或者说是顺时针旋转

然而上面这个右旋

旋转中心是节点4,旋转点是节点6,当旋转点(节点6)绕旋转中心节点(节点4)旋转,节点6旋转作为节点4的右孩子节点时,会和原本节点4的右孩子冲突,这时就需要让节点4的右孩子节点作为节点6的左孩子节点就行

记忆口诀:冲突的右孩作左孩

解释:冲突的右孩是指旋转中心的右孩子,做左孩是让旋转中心的右孩子作为旋转点的左孩子,旋转点作为旋转中心的右孩子

3.左右双旋(LR):

失衡节点的平衡因子为2,失衡节点右孩子节点的平衡因子为-1

左右双旋需要做两步:

第一步:对失衡节点的左孩子节点进行左旋

第二步:对失衡节点进行右旋

第一步解析:

对失衡节点的左孩子节点进行左旋,找到失衡节点6,他的左孩子节点为节点3,对其进行左旋

旋转点:节点3

旋转中心:节点5

旋转:节点3绕节点5逆时针旋转,节点3作为节点5的左孩子节点与节点4冲突,节点4作为旋转点节点3的右孩子(冲突的左孩作右孩)

第二步解析:

对失衡节点进行右旋

旋转点:节点6

旋转中心:节点5

旋转:节点6绕节点5顺时针旋转

4.右左双旋(RL):

失衡节点的平衡因子为-2,失衡节点右孩子节点的平衡因子为1

右左双旋同样需要两步完成

第一步:对失衡节点的有孩子节点进行右旋

第二步:对失衡节点进行左旋

第一步解析:

对失衡节点的右孩子节点进行右旋,找到失衡节点4,他的右孩子节点为节点8,对其进行右旋

旋转点:节点8

旋转中心:节点5

旋转:节点8绕节点5顺时针旋转,节点8作为节点5的右孩子节点与节点7冲突,节点7作为旋转点节点8的左孩子(冲突的右孩作左孩)

第二步解析:

对失衡节点进行左旋

旋转点:节点4

旋转中心:节点5

旋转:节点4绕节点5逆时针旋转

5.插入节点有多个祖先失衡

当多个祖先同时失衡时,只需要调整最近失衡的节点就可使平衡二叉树保持平衡

其他失衡节点会自动变平衡

四.平衡二叉树插入操作代码编写(JAVA)

1.创建一个节点类(avlTree类里的成员内部类)

创建一个节点类,用来存储一个应该具有的信息

    static class treeNode{
        int val;//节点存储的值
        treeNode left;//左孩子节点
        treeNode right;//右孩子节点
        treeNode parent;//父亲节点
        int bf;//平衡因子
        //构造方法
        public treeNode(int val) {
            this.val = val;
        }
    }

一个节点类里面应该具有

1.该节点所存储的值

2.左右孩子节点

3.父亲节点

4.平衡因子

5.构造方法

2.创建一个指向根节点的成员变量

  public treeNode root;

3.书写插入方法

3.1实例化节点,判断树是否为空

        treeNode node=new treeNode(val);//调用构造方法,实例化节点
        if(root==null){
            root=node;
        }//如果树为空,就将新创建的节点作为根节点

3.2寻找插入节点的位置

        treeNode parent=null;//parent作为插入节点的父亲节点
        treeNode cur=root;//cur用作寻找插入节点的位置
        while(cur!=null){//当cur为空时,cur所指位置就是插入节点位置
            if(cur.val<val){//如果cur所指节点的值小于插入节点的值,就向cur所指节点的右树寻找
                parent=cur;//先让parent指向cur
                cur=cur.right;//cur再指向右孩子节点
            }else if(cur.val==val){//如果cur所指节点的值和插入节点值相等,直接返回false
                return false;//这是因为平衡二叉树也是二叉搜索树,在二叉搜索树里,不能存在两个值相同的节点
            }else {//如果cur所指节点的值大于插入节点的值,就向cur的左树去寻找
                parent=cur;
                cur=cur.left;
            }
        }
//当找到插入位置时,即cur==null,需要判断一下插入节点的值和parent值的大小关系,大于parent,插入parent的右孩子节点,反之插入左孩子节点
        if(parent.val>val){
            parent.left=node;
            node.parent=parent;
        }else {
            parent.right=node;
            node.parent=parent;
        }
        cur=node;//最后记得将cur指向插入节点

特别注意:最后一定要将cur指向插入节点,为接下来调整平衡做准备

3.3调整平衡

        while(parent!=null){
            if(cur==parent.left){
                parent.bf++;
            }else {
                parent.bf--;
            }

            if(parent.bf==0){
                return true;
            }else if(parent.bf==-1||parent.bf==1){
                cur=parent;
                parent=parent.parent;
            }else {
                if(parent.bf==2){
                    if(cur.bf==1){//右旋
revolveLL(parent);
                    }else {//左右双旋
revolveLR(parent);
                    }
                }else {
                    if(cur.bf==1){//右左双旋
revolveRL(parent);
                    }else {//左旋
revolveRR(parent);
                    }
                }
                break;
            }
        }
return true;

 1.向上调整平衡因子,如果cur是parent的左孩子节点,那么parent的平衡因子++,如果是右孩子节点,parent的平衡因子--

2.每调整一个父亲节点的平衡因子,就判断一下,如果父亲节点的平衡因子为0,说明此时以父亲节点为根节点的左右子树高度相等,处于平衡状态

如果父亲节点的平衡因子的绝对值为1,就需要继续往上进行平衡因子的调节

如果父亲节点的平衡因子的绝对值是2,那么说明以父亲节点为根节点的树失衡,就需要根据父亲节点的平衡因子和cur的平衡因子,对树进行旋转操作

3.当执行完旋转操作调节平衡后,树就平衡了,直接跳出循环,返回true

当父亲节点的平衡因子为2时:

说明左树高,这时cur一定是左孩子节点,在通过cur的平衡因子判断失衡模型

如果cur的平衡因子是1,那么就是LL型,需要进行右旋

如果cur的平衡因子是-1,那么就是LR型,需要进行左右双旋


当父亲节点的平衡因子为-2时:

说明右树高,这时cur一定是右孩子节点,在通过cur的平衡因子判断失衡模型

如果cur的平衡因子是1,那么就是RL型,需要进行右左双旋

如果cur的平衡因子是-1,那么就是RR型,需要进行左旋

3.4旋转代码

3.4.1左旋代码
    private void revolveRR(treeNode parent){
treeNode subR=parent.right;//旋转中心
treeNode subRL=subR.left;//冲突的左孩
        //冲突的左孩是否为空
        if(subRL!=null){
            subRL.parent=parent;
        }
        parent.right=subRL;
        //父亲节点是否为根节点
        if(parent==root){
            subR.parent=null;
            root=subR;
        }else {
            treeNode pParent=parent.parent;
            if(parent==pParent.left){
                pParent.left=subR;
            }else {
                pParent.right=subR;
            }
            subR.parent=pParent;
        }
        //处理旋转点和旋转中心的关系
        parent.parent=subR;
        subR.left=parent;
        //修改平衡因子
        parent.bf=0;
        subR.bf=0;
    }

 左旋,无论是简单的还是复杂的,都只有旋转点和旋转中心的平衡因子被修改

3.4.2右旋代码
    private void revolveLL(treeNode parent){
treeNode subL=parent.left;//旋转中心
treeNode subLR=subL.right;//冲突的右孩

        //处理右孩为空的情况
if(subLR!=null){
    subLR.parent=parent;
}
parent.left=subLR;//右孩和旋转点建立联系
//处理旋转点是否是根节点的情况
        if(parent==root){
            root=subL;
            subL.right=parent;
        }else{
           treeNode pParent=parent.parent;
           if(parent==pParent.left){
               pParent.left=subL;
           }else {
               pParent.right=subL;
           }
           subL.parent=pParent;
        }
        //旋转中心和旋转点建立联系
        parent.parent=subL;
        subL.right=parent;
        //修改平衡因子
subL.bf=parent.bf=0;
    }

对于右旋操作来说,也只有旋转中心和旋转点的平衡因子需要修改 

3.4.3左右双旋代码
    private void revolveLR(treeNode parent){
        treeNode subL=parent.left;
        treeNode subLR=subL.right;
        int bf=subLR.bf;
revolveRR(parent.left);//先左旋父亲节点的左孩子节点
revolveLL(parent);//再右旋父亲节点
        if(bf==1){
            parent.bf=-1;
            subL.bf=0;
            subLR.bf=0;
        }else if(bf=-1){
            parent.bf=0;
            subL.bf=1;
            subLR.bf=0;
        }
    }

左右双旋时,需要根据subLR的平衡因子来确定最后怎么修改平衡因子 

 对于这种subLR的平衡因子为0的,在右旋和左旋过程中,旋转点和旋转中心的节点,已经被修改为0了,就无需再对其进行修改了

3.4.4右左双旋
    private void revolveRL(treeNode parent){
treeNode subR=parent.right;
treeNode subRL=subR.left;
int bf=subRL.bf;
revolveLL(parent.right);
revolveRR(parent);
if(bf==1){
            parent.bf=0;
            subRL.bf=0;
            subR.bf=-1;
}else if(bf==-1){
    parent.bf=1;
    subRL.bf=0;
    subR.bf=0;
}
    }

右左双旋时,同样也需要根据subRL的平衡因子对调整后的平衡因子做对应的修改

对于这种subRL的平衡因子为0的,在右旋和左旋过程中,旋转点和旋转中心的节点,已经被修改为0了,就无需再对其进行修改了

 五.判断一棵树是否是平衡二叉树

平衡二插树首先是一棵二叉搜索树,所以他的中序遍历是有序的,在其次就是他的每个节点的左右树高的差的绝对值不能大于1,通过这两个特性,我们就能判定一棵树是否是平衡二叉树

1.中序遍历:

    public void inOrder(treeNode root){
        if(root==null){
            return;
        }
        inOrder(root.left);
        System.out.print(root.val+" ");
        inOrder(root.right);
    }

2.判断每个节点左右树高差的绝对值

    public boolean isBalanced(treeNode root){
        if(root==null){
            return true;
        }
        int leftH=height(root.left);
        int rightH=height(root.right);
        //判断左右树高差的绝对值是否小于等于1,并且递归判断每一个节点是否满足要求
        return Math.abs(leftH-rightH)<=1&&isBalanced(root.left)&&isBalanced(root.right);
    }

//获取左右树高
    private int height(treeNode node){
        if(node==null){
            return 0;
        }
        int leftH=height(node.left);
        int rightH=height(node.right);
        return Math.max(leftH,rightH)+1;
    }

六.AVL树性能分析

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即log(n) 。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

                                                                                                                                   努力吧!少年 

评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值