前言
上一篇博客中讲解了二叉查找树的实现,但是我们在对其进行删除操作时,采用的是用被删除节点的右子树中的最小元素代替该节点,并将最小元素节点删除,这样导致的后果可想而知,再经过不断的remove操作后,整棵树会处于极度不平衡状态,会导致该树的左子树过深,而右子树过浅的问题。要想解决这种问题,就需要实现一种平衡查找树。
平衡查找树
我们这里将介绍一种古老的平衡查找树————AVL树。
平衡的条件:任何节点的深度均不得过深。
一棵AVL树是其每个节点的左子树和右子树的高度最多差1的二叉查找树。在AVL树中每个节点都会保存该节点的高度信息。
在上图中,左边的是AVL平衡树,因为它的每个节点的左子树与右子树高度相差都不超过1,而右边的不是AVL树,因为在它的根节点处左子树高度为3,右子树高度为1,相差超过了1,因此它不是一棵AVL平衡树。
当我们对一棵二叉树进行插入或是删除操作时会有以下四种情况出现不平衡状态:
- 对某个节点的左儿子的左子树进行一次插入/删除(情况一)
- 对某个节点的左儿子的右子树进行一次插入/删除(情况二)
- 对某个节点的右儿子的左子树进行一次插入/删除(情况三)
- 对某个节点的右儿子的右子树进行一次插入/删除(情况四)
当出现这种不平衡状态时我们就需要通过旋转来使该树达到平衡状态。旋转分为单旋转与双旋转,而单旋转又分为左旋转与右旋转,双旋转分为左右旋转与右左旋转。
对于被破坏平衡的节点a来说:
不平衡情况 | 描述 | 旋转方式 |
---|---|---|
情况一 | 在a的左儿子的左子树上插入节点从而破坏平衡 | 右旋转 |
情况二 | 在a的左儿子的右子树上插入节点从而破坏平衡 | 先左旋后右旋 |
情况三 | 在a的右儿子的左子树上插入节点从而破坏平衡 | 先右旋后左旋 |
情况四 | 在a的右儿子的右子树上插入节点从而破坏平衡 | 左旋转 |
旋转
1.右旋转
当在一个节点的左子树的左儿子上插入一个节点破坏平衡时就需要对被破坏的节点进行平衡。例如有下面这样一棵树:
此时我们插入一个节点2后树结构如下
可以很明显的看到此时该树处于不平衡状态,因为对于节点4来说,它的左子树高度为2,右子树高度为0,此时我们就需要对该树进行平衡。需要注意的是,我们只需要对从新插入的节点到根节点路径上最近的一个不平衡节点进行旋转即可,例如上图中节点5也不平衡,但我们只需要平衡最近的节点4。
对于节点4来说,我们是在它的左子树的左儿子上进行插入,如情况一所示,于是需要对其进行右旋转。此时我们可以闭着眼睛,然后把节点3向上一提,在重力作用下,节点4就变成了3的右子树。右旋过程如下:
此时我们发现树中所有的节点都符合了平衡条件,所以整棵树就变成了AVL树。
2.左右旋转
现在我们有下面这棵处于平衡状态的树结构:
此时需要插入一个节点5,插入后树的平衡状态被破坏
此时我们发现不管是左旋还是右旋,都无法将树恢复成平衡状态。于是只能通过先左旋再右旋的方式将它转换成一棵平衡树。
这里我只介绍了右旋转以及左右旋转,左旋转跟右左旋转方式相同。通过适当的旋转我们可以发现树的深度降低了并且也平衡了左右子树上的节点。
代码解析
对于一棵AVL树,我们需要在每个节点处记录它的高度信息。AVL树的代码与二叉查找树的代码类似,不同的时在每次进行插入或是删除后都需要调用balance(AVLNode<T> t)
方法来平衡树结构,以及需要有一个可以返回节点高度信息的方法。
为了更详细的阅读,下面我还是给出AVL树的所有代码:
/**
* @author: zhangocean
* @Date: 2018/10/12 18:51
*/
public class AVLTree<T extends Comparable<? super T>>{
private AVLNode<T> root;
private class AVLNode<T>{
private T element;
private AVLNode<T> left;
private AVLNode<T> right;
private int height; //存储高度信息
public AVLNode(T element) {
this.element = element;
this.left = null;
this.right = null;
}
public AVLNode(T element, AVLNode<T> left, AVLNode<T> right) {
this.element = element;
this.left = left;
this.right = right;
}
}
//返回树的高度
public int getHeight(AVLNode<T> t){
return t == null ? 0 : t.height;
}
public void insert(T element){
root = insert(element, root);
}
private AVLNode<T> insert(T element, AVLNode<T> t){
if(t == null){
return new AVLNode<T>(element);
}
int compareResult = element.compareTo(t.element);
if(compareResult > 0){
t.right = insert(element, t.right);
} else if (compareResult < 0){
t.left = insert(element, t.left);
}
return balance(t);
}
/**
* 平衡树
*/
private AVLNode<T> balance(AVLNode<T> t){
if(t == null){
return t;
}
//左子树深度过深
if(getHeight(t.left) - getHeight(t.right) > 1){
//情况一
if(getHeight(t.left.left) >= getHeight(t.left.right)){
t = rotateWithLeftChild(t);
}
//情况二
else {
t = doubleWithLeftChild(t);
}
}
//右子树深度过深
else if (getHeight(t.right) - getHeight(t.left) > 1){
//情况四
if(getHeight(t.right.right) >= getHeight(t.right.left)){
t = rotateWithRightChild(t);
}
//情况三
else {
t = doubleWithRightChild(t);
}
}
t.height = Math.max(getHeight(t.left), getHeight(t.right)) + 1;
return t;
}
/**
* 左旋
*/
private AVLNode<T> rotateWithRightChild(AVLNode<T> t1){
AVLNode<T> t2 = t1.right;
t1.right = t2.left;
t2.left = t1;
t1.height = Math.max(getHeight(t1.left), getHeight(t1.right)) + 1;
t2.height = Math.max(getHeight(t2.left), getHeight(t2.right)) + 1;
return t2;
}
/**
* 右旋
*/
private AVLNode<T> rotateWithLeftChild(AVLNode<T> t1){
AVLNode<T> child = t1.left;
t1.left = child.right;
child.right = t1;
t1.height = Math.max(getHeight(t1.left), getHeight(t1.right)) + 1;
child.height = Math.max(getHeight(child.left), getHeight(child.right)) + 1;
return child;
}
/**
* 左右旋
*/
private AVLNode<T> doubleWithLeftChild(AVLNode<T> t){
t.left = rotateWithRightChild(t.left);
return rotateWithLeftChild(t);
}
/**
* 右左旋
*/
private AVLNode<T> doubleWithRightChild(AVLNode<T> t){
t.right = rotateWithLeftChild(t.right);
return rotateWithRightChild(t);
}
public void remove(T element){
root = remove(element, root);
}
private AVLNode<T> remove(T element, AVLNode<T> t){
if(t == null){
return t;
}
int compareResult = element.compareTo(t.element);
if(compareResult > 0){
t.right = remove(element, t.right);
} else if (compareResult < 0){
t.left = remove(element, t.left);
} else if (t.left != null && t.right != null){
t.element = findMin(t.right).element;
t.right = remove(t.element, t.right);
} else {
t = t.left == null ? t.right : t.left;
}
return balance(t);
}
public T findMin(){
return findMin(root).element;
}
public AVLNode<T> findMin(AVLNode<T> t){
if(t == null){
return null;
}
if(t.left != null){
return findMin(t.left);
}
return t;
}
public void printTree(){
if(root == null){
System.out.println("空树");
} else {
printTree(root);
}
}
public void printTree(AVLNode<T> t){
if(t != null){
printTree(t.left);
System.out.print(t.element + " ");
printTree(t.right);
}
}
}
其中对于树的插入以及删除等可以查看上篇文章,这里主要介绍如何平衡树已经如果旋转。
先来说说树的平衡。在balance(AVLNode<T> t)
方法中,我们通过59以及70行代码判断出左右子树的不平衡节点,然后再根据不平衡节点左右高度来进行不同的旋转。在第61以及72行处我们使用>=
而不是>
来保证在=
的情况下用的是单旋转而不是双旋转。当我们旋转完成后需要修改该节点的高度,可通过获得其左右子树高度的最大值加一来获得它的高度。
对于树的旋转这里,就只拿右旋举栗:
我们需要将节点b上移一层,并将原先的根节点a作为节点b的右子树,因此我们需要通过节点a获得其右子树(即节点b),再将节点a作为节点b的右子树即可,需要注意的是我们同时需要将节点a的left链置为null。最后由下而上的修改每个节点的高度,就完成了右旋过程。
更多文章请关注我的个人博客:www.zhyocean.cn