🔥个人主页🔥:鱼骨不是鱼翅-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指向curcur=cur.right;//cur再指向右孩子节点}else if(cur.val==val){//如果cur所指节点的值和插入节点值相等,直接返回falsereturn 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树,但一个结构经常修改,就不太适合。
努力吧!少年