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));
}
}