版权声明:本文为博主原创文章,转载请注明出处,https://blog.csdn.net/u014165620/article/details/82492099
###一、简介
AVL树是带有平衡条件的二叉查找树(Binary Search Tree),在理解AVL树之前,需要对二叉查找树有所了解。
名词解释
树高度(height):根节点为root的树的**高度(height)**是从root到其叶节点的最长路径的长,空树高度定义为-1。
当一个二叉查找树(BST)的左右子树高度差比较大时,会影响查询类操作效率。先看一个直观的描述,以如下极端情况为例:
对左边二叉查找树做查找操作时,与链表结构查询操作一样,效率较低,所以需要对其结构进行相应调整,使其左右子树平衡,从而提高查询效率。调整后的结构如图右边所示,这就是一棵AVL树。
####定义
一棵AVL树就是,其每个节点的左子树与右子树的高度差不超过1的二叉查找树。
###二、旋转(rotation)
每次插入新节点时,从插入点到根节点的路径上的节点的平衡性可能被破坏,因为这些节点的子树发生了变化,所以需要自下而上依次检验并修正这些节点的平衡性。如果发生破坏的情况,可以通过对树进行简单的修正来恢复其平衡特性——旋转(rotation)。
我们把平衡被破坏的节点叫做a,根据插入点相对于a的位置,可以分为4中情况:
插入位置 | 描述 | 旋转方式
-------- | —
LL | 在a的左(L)节点的左(L)子树上插入新节点 | 右旋转
RR | 在a的右®节点的右®子树上插入新节点 | 左旋转
LR | 在a的左(L)节点的右®子树上插入新节点 | 先左旋转,再右旋转
RL | 在a的右®节点的左(L)子树上插入新节点 | 先右旋转,再左旋转
前两种情况为单旋转(single rotation),后两种为双旋转(double rotation)。下面结合图片对每种情况进行介绍。
####LL 右旋转
插入1之后节点3的左子树高度为1,右子树为空,高度为-1,高度差为2,不平衡,为LL类型,需要右旋转。图中连接节点3与其左子树(高度较高的子树)的虚线为旋转主体。更直观通俗的解释就是,抓着节点2往上提,节点3掉到2的右边,成为2的右子树。
再看一个稍复杂的例子:
插入12之后根节点20的左子树高度为2,右子树高度为0,高度差为2,不平衡,在20的左节点的左子树上插入导致的不平衡,为LL型,仍然需要右旋转。同样的,图中连接20、17的虚线为旋转主体,抓着17往上提,使20掉到17的右边,变为17的右节点。此时问题来了,17原来的右节点18怎么处理呢?因为17原来的右节点肯定比17大,比20小,所以直接变为20的左节点就可以了。
RR 左旋转
RR 左旋转与 LL 右旋转类似,这是旋转方向向左而已,下面直接用两个例子说明:
LR 先左旋,再右旋
在不平衡节点a的左(L)节点的右®子树上插入导致的不平衡,需要自下而上,先左旋,再右旋:
插入4后导致节点5不平衡,因为是在5的左节点的右子树上插入导致的不平衡,为LR型,图中连接节点5、5的左(L)节点、5的左节点的右®子树三个点的两条虚线为旋转主体,需要自下而上先左旋,再右旋,一共两次旋转,每次旋转操作都和上面讲的单旋转操作一致。
再看一个稍微复杂一点LR型的例子:
####RL 先右旋,再左旋
插入7后导致节点5不平衡,因为是在5的右®节点的左(L)子树上插入导致的不平衡,为RL型,图中连接节点5、5的右®节点、5的右节点的左(L)子树三个点的两条虚线为旋转主体,需要自下而上先右旋,再左旋,一共两次旋转,每次旋转操作都和上面讲的单旋转操作一致。
###三、Java实现
因为AVL树就是带有平衡条件的二叉查找树,只是在二叉查找树的实现基础上增加平衡特性的实现,涉及到处理平衡特性的操作只有插入、删除,所以在这里只对AVL树的插入、删除操作进行简单实现,其他查找、遍历等操作实现,请参考另一篇专门介绍二叉查找树(Binary Search Tree)的博客。
####定义
使用一个内部类AVLNode定义AVL树节点,节点值数据类型需要支持比较大小。
public class AVLTree<T extends Comparable<T>> {
//允许的高度差,超过该值即视为不平衡,此处定义为1
private static final int ALLOWED_HEIGHT_DIFF = 1;
private AVLNode<T> root;
//使用内部类定义AVL树节点
private static class AVLNode<T extends Comparable<T>>{
T key; //节点值,需要支持比较大小
AVLNode<T> left; //左节点
AVLNode<T> right; //右节点
int hight; //高度
public AVLNode(T key){
this(key, null, null);
}
public AVLNode(T key, AVLNode<T> left, AVLNode<T> right){
this.key = key;
this.left = left;
this.right = right;
this.hight = 0;
}
}
...
}
####插入,通过旋转处理平衡特性
每插入一个新节点,都需要检查树的平衡性,如果插入后树的平衡性被破坏,则需要通过旋转来恢复其平衡特性。
/**
* 获取树高度,空树高度为-1
* @param root 根节点
* @return 树高度
*/
public int getHeight(AVLNode<T> root){
return root == null ? -1 : root.height;
}
/**
* 插入值为key的节点
* @param key 插入值
* @param root 根节点
* @return 新的根节点
*/
public AVLNode<T> insert(T key, AVLNode<T> root){
//根节点为空,直接定义一个新节点作为根节点返回
if(root == null){
return new AVLNode<T>(key);
}
//将待插入值与当前节点值比较
int cmp = key.compareTo(root.key);
//小于,在左子树插入
if(cmp < 0){
root.left = insert(key, root.left);
}
//大于,在右子树插入
else if(cmp > 0){
root.right = insert(key, root.right);
}
//等于,节点值重复,不插入
else{
System.out.println("插入失败,树中值为"+key+"的节点已经存在");
}
return balance(root);
}
/**
* 处理平衡特性
* @param root 根节点
* @return 平衡后新的根节点
*/
private AVLNode<T> balance(AVLNode<T> root){
if(root == null){
return null;
}
//左右子树高度差超过允许值,且左子树较高,即在左子树插入导致不平衡
if(getHeight(root.left) - getHeight(root.right) > ALLOWED_HEIGHT_DIFF){
//在左(L)节点的左(L)子树插入导致不平衡,单旋转调整,右旋转
if(getHeight(root.left.left) >= getHeight(root.left.right)){
root = rotateWithRight(root);
}
//在左(L)节点的左(右)子树插入导致不平衡,双旋转调整,先左旋,再右旋
else{
root = rotateWithLeftRight(root);
}
}
//左右子树高度差超过允许值,且右子树较高,即在右子树插入导致不平衡
else if(getHeight(root.right) - getHeight(root.left) > ALLOWED_HEIGHT_DIFF){
//在右(R)节点的右(R)子树插入导致不平衡,单旋转调整, 左旋转
if(getHeight(root.right.right) >= getHeight(root.right.left)){
root = rotateWithLeft(root);
}
//在右(R)节点的左(L)子树插入导致不平衡,双旋转调整,先右旋,再左旋
else{
root = rotateWithRightLeft(root);
}
}
root.height = Math.max(getHeight(root.left), getHeight(root.right)) + 1;
return root;
}
/**
* LL型,右旋转
* 5
* 4
* 3
* @param node5 不平衡的节点(5)
* @return 平衡后新的根节点(4)
*/
private AVLNode<T> rotateWithRight(AVLNode<T> node5){
AVLNode<T> node4 = node5.left;
//原node4的右节点变为调整后node5的左节点
node5.left = node4.right;
//将node4向上提,node5掉为node4的右节点
node4.right = node5;
//更新高度
node5.height = Math.max(getHeight(node5.left), getHeight(node5.right)) + 1;
node4.height = Math.max(getHeight(node4.left), node5.height) + 1;
return node4;
}
/**
* RR型,左旋转
* 5
* 4
* 3
* @param node5 不平衡的节点(5)
* @return 平衡后新的根节点(4)
*/
private AVLNode<T> rotateWithLeft(AVLNode<T> node5){
AVLNode<T> node4 = node5.right;
//原node4的左节点变为调整后node5的右节点
node5.right = node4.left;
//将node4向上提,node5掉为node4的左节点
node4.left = node5;
//更新高度
node5.height = Math.max(getHeight(node5.left), getHeight(node5.right)) + 1;
node4.height = Math.max(getHeight(node4.right), node5.height) + 1;
return node4;
}
/**
* LR型, 先左旋,再右旋
* 5 5 4
* 4 4 3 5
* 3 , 3 ,
* @param node5 不平衡的节点(5)
* @return 平衡后新的根节点(4)
*/
private AVLNode<T> rotateWithLeftRight(AVLNode<T> node5){
//先左旋
node5.left = rotateWithLeft(node5.left);
//再右旋
return rotateWithRight(node5);
}
/**
* RL型, 先右旋,再左旋
* 5 5 4
* 4 4 3 5
* 3 , 3 ,
* @param node5 不平衡的节点(5)
* @return 平衡后新的根节点(4)
*/
private AVLNode<T> rotateWithRightLeft(AVLNode<T> node5){
//先右旋
node5.right = rotateWithRight(node5.right);
//在左旋
return rotateWithLeft(node5);
}
####删除
二叉查找树的删除操作已经比较复杂,AVL树的删除操作需要再额外考虑平衡特性处理,进一步提升复杂度,所以对于AVL树的删除操作,个人更倾向于使用懒惰删除,即删除节点时,节点仍留在树中,只是标记为“已删除”,以此降低删除操作复杂度,业务处理时,遇到被标记为“已删除”的节点直接跳过不处理。即逻辑删除,而不是物理删除。
下面是物理删除代码:
/**
* 在以root为根节点的树中删除值为key的节点
* @param key
* @param root
* @return 删除并处理平衡后新的根节点
*/
private AVLNode<T> remove(T key, AVLNode<T> root){
if(root == null)
return null;
int cmp = key.compareTo(root.key);
if(cmp < 0){
root.left = remove(key, root.left);
//完了之后验证该子树是否平衡
if(root.right != null){ //若右子树为空,则一定是平衡的,此时左子树相当对父节点深度最多为1, 所以只考虑右子树非空情况
if(root.left == null){ //若左子树删除后为空,则需要判断右子树
if(getHeight(root.right)-root.height == 2){
AVLNode<T> k = root.right;
if(k.right != null){ //右子树存在,按正常情况单旋转
root = rotateWithLeft(root);
}else{ //否则是右左情况,双旋转
root = rotateWithRightLeft(root);
}
}
}else{ //否则判断左右子树的高度差
//左子树自身也可能不平衡,故先平衡左子树,再考虑整体
AVLNode<T> k = root.left;
//删除操作默认用右子树上最小节点补删除的节点
//k的左子树高度不低于k的右子树
if(k.right != null){
if(getHeight(k.left)-getHeight(k.right) == 2){
AVLNode<T> m = k.left;
if(m.left != null){ //左子树存在,按正常情况单旋转
k = rotateWithRight(k);
}else{ //否则是左右情况,双旋转
k = rotateWithLeftRight(k);
}
}
}else{
if(getHeight(k.left) - k.height ==2){
AVLNode<T> m = k.left;
if(m.left != null){ //左子树存在,按正常情况单旋转
k = rotateWithRight(k);
}else{ //否则是左右情况,双旋转
k = rotateWithLeftRight(k);
}
}
}
if(getHeight(root.right)-getHeight(root.left) == 2){
//右子树自身一定是平衡的,左右失衡的话单旋转可以解决问题
root = rotateWithLeft(root);
}
}
}
//完了之后更新height值
root.height = Math.max(getHeight(root.left), getHeight(root.right))+1;
}else if(cmp > 0){
root.right = remove(key, root.right);
//下面验证子树是否平衡
if(root.left != null){ //若左子树为空,则一定是平衡的,此时右子树相当对父节点深度最多为1
if(root.right == null){ //若右子树删除后为空,则只需判断左子树
if(getHeight(root.left)-root.height ==2){
AVLNode<T> k = root.left;
if(k.left != null){
root = rotateWithRight(root);
}else{
root = rotateWithLeftRight(root);
}
}
}else{ //若右子树删除后非空,则判断左右子树的高度差
//右子树自身也可能不平衡,故先平衡右子树,再考虑整体
AVLNode<T> k = root.right;
//删除操作默认用右子树上最小节点(靠左)补删除的节点
//k的右子树高度不低于k的左子树
if(k.left != null){
if(getHeight(k.right)-getHeight(k.left) == 2){
AVLNode<T> m = k.right;
if(m.right != null){ //右子树存在,按正常情况单旋转
k = rotateWithLeft(k);
}else{ //否则是右左情况,双旋转
k = rotateWithRightLeft(k);
}
}
}else{
if(getHeight(k.right)-k.height == 2){
AVLNode<T> m = k.right;
if(m.right != null){ //右子树存在,按正常情况单旋转
k = rotateWithLeft(k);
}else{ //否则是右左情况,双旋转
k = rotateWithRightLeft(k);
}
}
}
if(getHeight(root.left) - getHeight(root.right) == 2){
//左子树自身一定是平衡的,左右失衡的话单旋转可以解决问题
root = rotateWithRight(root);
}
}
}
//完了之后更新height值
root.height = Math.max(getHeight(root.left), getHeight(root.right))+1;
}else if(root.left != null && root.right != null){
//默认用其右子树的最小数据代替该节点的数据并递归的删除那个节点
root.key = findMin(root.right).key;
root.right = remove(root.key, root.right);
if(root.right == null){ //若右子树删除后为空,则只需判断左子树与根的高度差
if(getHeight(root.left)-root.height ==2){
AVLNode<T> k = root.left;
if(k.left != null){
root = rotateWithRight(root);
}else{
root = rotateWithLeftRight(root);
}
}
}else{ //若右子树删除后非空,则判断左右子树的高度差
//右子树自身也可能不平衡,故先平衡右子树,再考虑整体
AVLNode<T> k = root.right;
//删除操作默认用右子树上最小节点(靠左)补删除的节点
if(k.left != null){
if(getHeight(k.right)-getHeight(k.left) == 2){
AVLNode<T> m = k.right;
if(m.right != null){ //右子树存在,按正常情况单旋转
k = rotateWithLeft(k);
}else{ //否则是右左情况,双旋转
k = rotateWithRightLeft(k);
}
}
}else{
if(getHeight(k.right)-k.height == 2){
AVLNode<T> m = k.right;
if(m.right != null){ //右子树存在,按正常情况单旋转
k = rotateWithLeft(k);
}else{ //否则是右左情况,双旋转
k = rotateWithRightLeft(k);
}
}
}
//左子树自身一定是平衡的,左右失衡的话单旋转可以解决问题
if(getHeight(root.left) - getHeight(root.right) == 2){
root = rotateWithRight(root);
}
}
//完了之后更新height值
root.height = Math.max(getHeight(root.left), getHeight(root.right))+1;
}else{
root = (root.left != null)?root.left:root.right;
}
return root;
}
测试:
public static void main(String[] args){
AVLTree<Integer> avl = new AVLTree<Integer>();
for (int i=0; i<5; i++){
avl.root = avl.insert(i, avl.root);
}
System.out.println(avl.getHeight(avl.root));
}
运行结果:
构造AVL树结构如下:
以上就是AVL树介绍,包含旋转图解及Java实现。
个人总结,如有错误,感谢指正!
参考《数据结构与算法分析 Java语言描述》
删除方法代码参考:https://blog.csdn.net/liyong199012/article/details/29219261