AVL树详解(实现AVL树,涉及单旋和双旋,有图解,超详细)

AVL树是什么?

在讲AVL树是什么之前,我们要先知道什么是二叉搜索树
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树
    二叉搜索树的搜索效率很高,能达到(logn)的时间复杂度,但是如果说二叉搜索树的元素是一个有序序列,那么就会变成一个单分支树搜索的时间复杂度最坏就变成了O(N),如下图,就是二叉搜索变成了单分支树
    在这里插入图片描述
    此时就引入AVL树进行优化,看AVL树的需要满足的条件:
  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
    在这里插入图片描述

AVL树其实就是在二叉搜索树插入新元素时,维护平衡因子的绝对值不超过1而已,接下来就来实现一下AVL树的插入操作

AVL树的插入操作

很明显,AVL树的结点我们不仅需要左右孩子的引用,还得定义一个字段作为平衡因子(右子树高度-左子树高度),但是我们还要定义一个指向父亲节点的引用,方便我们进行插入操作之后,向上调整平衡因子,看下面代码对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;
        }
    }


插入一共有三个步骤:

  • 寻找插入位置
  • 将节点插入
  • 调整平衡因子
    前面两步没什么好讲的,基本更二叉搜索树没有区别

寻找插入位置

话不多说,直接看代码

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) {
                return false;
            } else {
                parent = cur;
                cur = cur.left;
            }
        }

将节点插入

也是 直接 看代码

//cur==null
        if(parent.val<val) {
            parent.right = node;
        } else {
            parent.left = node;
        } 
        node.parent = parent;

这里需要注意的是把新插入的结点的父亲节点引用更新

调节平衡因子

接着上面的,我们新插入了一个结点node,那么肯定就要向上调整平衡因子了嘛,要一直从该node节点调整到根节点的平衡因子,直到这棵树平衡了。
走到这里,我们看上面代码走到,此时parent == node.parent ,cur ==null,我们让cur=node;接着往下看
如果cur是parent的左孩子,那么parent的bf就需要-1(因为左子树高度增加了);如果cur是parent的右孩子,那么parent的bf就需要+1,(因为右子树高度增加了),那么在向上更新的过程中,有三种情况:
第一种情况当parent.bf 等于0时,此时这棵树已经平衡,看图
在这里插入图片描述
第二中情况当parent.bf等于1或者-1时,此时就还要继续向上调整平衡因子
在这里插入图片描述
**第三种情况,当parent.bf == 2||parent.bf == -2的时候,那么此时这棵树就不平衡了,我们就要进行旋转,此时有4种情况,我们先把大致框架的代码先写出来:

//调节平衡因子
        while(parent!=null) {
            if(parent.left==cur) {
                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) {
                        rotateLR(parent);
                    } else {
                        //cur.bf==-1
                        rotateRight(parent);
                    }
                }
                //完成一次旋转之后就能够达成平衡,直接break
                break;
            }
        }

看到上面代码,我们知道,旋转有4种情况,这里我告诉你,分别是右旋,左旋,左右双旋和右左双旋

AVL旋转调整

接着上面
第一种情况,当parent.bf ==-1 && cur.bf == -1 时,要进行右旋
如图
在这里插入图片描述
把subL看成中心,然后parent绕着subL向右手边旋转,然后把subL的右孩子给parent的左孩子即可
这里有一个细节,右旋完后,我们还要处理更新parent.parent的孩子指向,这里称为pParent:
如果parent 就是根节点,那我们让root == subL,然后让root.parent = null即可
如果pParent.left == parent || pParent.right ==parent,那么就更新一下pParent的孩子指向,然后让subL.parent = pParent即可,看图:
在这里插入图片描述
然后在更新一下subL和parent和bf,在这种情况subL.bf = parent.bf =0;看上图就可以知道
但是还有一种情况当subLR为null时,但是这种情况还是subL.bf = parent.bf =0,看图:
在这里插入图片描述

那么看一下右旋代码的实现:

    private void rotateRight(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR=subL.right;
        //记录一下parent的父亲节点
        TreeNode pParent = parent.parent;
        parent.left = subLR;
        //检查有没有subLR
        if(subLR!=null) {
            subLR.parent = parent;
        }
        subL.right = parent;
        parent.parent = subL;
        //检查当前是不是根节点
        if(parent == root) {
            root = subL;
            root.parent = null;
        } else {
            //不是根节点,判断这棵子树是左子树还是右子树
            if(pParent.left == parent) {
                pParent.left = subL;
            } else {
                pParent.right = subL;
            }
            subL.parent = pParent;
        }
        //更新平衡因子
        subL.bf = parent.bf = 0;
    }

第二种情况,当parent .bf=2 && cur.bf==1,要进行左旋
在这里插入图片描述

以subR为中心,让parent绕subR向左手边转,然后把subR的左孩子subRL给parent,然后把subR.bf = parent.bf=0,这里细节跟上面右旋差不多,就偷懒一下,直接看代码吧

private void rotateLeft(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;
        //记录一下parent的父亲节点
        TreeNode pParent = parent.parent;
        parent.right = subRL;
        //检查有没有subRL
        if(subRL!=null) {
            subRL.parent = parent;
        }
        subR.left = parent;
        parent.parent = subR;
        //检查当前是不是根节点
        if(parent==root) {
            root = subR;
            root.parent = null;
        } else {
            if(pParent.left == parent) {
                pParent.left = subR;
            } else {
                pParent.right = subR;
            }
            subR.parent = pParent;
        }
        //更新平衡因子
        subR.bf = parent.bf=0;
    }

第三种情况,当parent.bf==-2&&cur == 1,此时要进行左右双旋:
直接看图:
在这里插入图片描述
在这里插入图片描述
前面左旋,右旋都讲过了,这里就再偷懒一下,那么为什么我要画两幅图呢?
那当然是有原因的,你可以发现当插入的结点插入在subLR的左子树时(subLR.bf == -1),和当插入的结点插入在subLR的右子树时(当subLR.bf == 1),parent,subL,subLR的bf更新值是不一样的,所以共有三种情况,为什么两幅图会有三种情况呢,因为有一种情况不用处理

  • 当subLR.bf == -1,最终的平衡因子更新为:subLR = 0,subL=0,parent = 1
  • 当subLR.bf == 1,最终的平衡因子更新为:subLR= 0,subL = -1,parent =0
  • 当subLR.bf == 0 ,不用处理,这三点的bf值不会改变
    看代码:
private void rotateLR(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        int bf = subLR.bf;
        rotateLeft(parent.left);
        rotateRight(parent);
        //分情况调整,平衡因子
        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不需要做任何处理
    }

第四种情况就是,parebt.bf==2&& cur.bf ==-1,要使用右左双旋
还是一样,先看图
在这里插入图片描述

在这里插入图片描述
同样的右旋,左旋同样bf有三种情况

  • 当subRL.bf == -1,更新平衡因子,sunRL.bf=0,parent.bf =0,subR.bf=1
  • 当subRL.bf == 1,更新平衡因子,parent.bf = -1,subR.bf =0,subRL.bf=0
  • 当subRL.bf==0,不用处理
    看代码
 private void rotateRL(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;
        int bf = subRL.bf;
        rotateRight(parent.right);
        rotateLeft(parent);
        if(bf==-1) {
            subR.bf=1;
            subRL.bf=0;
            parent.bf=0;
        } else if(bf==1) {
            subR.bf = 0;
            subRL.bf = 0;
            parent.bf = -1;
        }
        //bf==0不需要处理
    }

写个isBalance验证一下是不是AVL树

public boolean isBalance(TreeNode root) {
        if(root == null) {
            return true;
        }
        int leftH = height(root.left);
        int rightH = height(root.right);
        if(rightH-leftH!=root.bf) {
            System.out.println("平衡因子异常 "+root.val);
            return false;
        }
        return Math.abs(rightH-leftH)<=1&&isBalance(root.left)&&isBalance(root.right);

    }

完整代码

package AVLTree;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: ling
 * Date: 2022-11-22
 * Time: 10:34
 */
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) {
                return false;
            } else {
                parent = cur;
                cur = cur.left;
            }
        }
        //cur==null
        if(parent.val<val) {
            parent.right = node;
        } else {
            parent.left = node;
        }
        node.parent = parent;
        cur = node;
        //调节平衡因子
        while(parent!=null) {
            if(parent.left==cur) {
                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) {
                        rotateLR(parent);
                    } else {
                        //cur.bf==-1
                        rotateRight(parent);
                    }
                }
                //完成一次旋转之后就能够达成平衡,直接break
                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);
        if(bf==-1) {
            subR.bf=1;
            subRL.bf=0;
            parent.bf=0;
        } else if(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);
        //分情况调整,平衡因子
        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的父亲节点
        TreeNode pParent = parent.parent;
        parent.right = subRL;
        //检查有没有subRL
        if(subRL!=null) {
            subRL.parent = parent;
        }
        subR.left = parent;
        parent.parent = subR;
        //检查当前是不是根节点
        if(parent==root) {
            root = subR;
            root.parent = null;
        } else {
            if(pParent.left == parent) {
                pParent.left = subR;
            } else {
                pParent.right = subR;
            }
            subR.parent = pParent;
        }
        //更新平衡因子
        subR.bf = parent.bf=0;
    }

    /**
     * 右单旋
     * @param parent
     */
    private void rotateRight(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR=subL.right;
        //记录一下parent的父亲节点
        TreeNode pParent = parent.parent;
        parent.left = subLR;
        //检查有没有subLR
        if(subLR!=null) {
            subLR.parent = parent;
        }
        subL.right = parent;
        parent.parent = subL;
        //检查当前是不是根节点
        if(parent == root) {
            root = subL;
            root.parent = null;
        } else {
            //不是根节点,判断这棵子树是左子树还是右子树
            if(pParent.left == parent) {
                pParent.left = subL;
            } else {
                pParent.right = subL;
            }
            subL.parent = pParent;
        }
        //更新平衡因子
        subL.bf = parent.bf = 0;
    }

    /**
     * 验证是否为二叉平衡树
     * @param root
     * @return
     */
    public boolean isBalance(TreeNode root) {
        if(root == null) {
            return true;
        }
        int leftH = height(root.left);
        int rightH = height(root.right);
        if(rightH-leftH!=root.bf) {
            System.out.println("平衡因子异常 "+root.val);
            return false;
        }
        return Math.abs(rightH-leftH)<=1&&isBalance(root.left)&&isBalance(root.right);

    }
    /**
     * 求树的高度
     */
    private int height(TreeNode root) {
        if(root == null) {
            return 0;
        }
        int leftH =height(root.left);
        int rightH = height(root.right);
        return leftH>rightH?leftH+1:rightH+1;
    }


    public static void main(String[] args) {
        AVLTree avlTree1 = new AVLTree();
        AVLTree avlTree2 = new AVLTree();
        int[] arr1 ={16, 3, 7, 11, 9, 26, 18, 14, 15};
        int[] arr2 ={4, 2, 6, 1, 3, 5, 15, 7, 16,14};
        for(int i=0;i<arr1.length;i++) {
            avlTree1.insert(arr1[i]);
        }
        System.out.println(avlTree1.isBalance(avlTree1.root));
        for(int i=0;i<arr2.length;i++) {
            avlTree2.insert(arr2[i]);
        }
        System.out.println(avlTree2.isBalance(avlTree2.root));
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值