平衡二叉树的定义:
1. 平衡二叉树是一棵二叉查找树,满足所有二叉查找树的性质
2. 平衡二叉树要求任意一个节点的左右子树的高度差不能超过1
对于高度差,有一个专有名词平衡因子。
平衡因子:左子树的高度减去右子树的高度,及B = B左 - B右。由平衡二叉树的定义可知,平衡因子的取值只可能为0,1,-1。0:左右子树等高。1:左子树比较高。-1:右子树比较高。
高度:树中层级的数量。比如只有 Level 0,Level 1,Level 2 则高度为 3。一般的我们取叶子节点的高度值为1,任意一个节点的高度值取左右子树比较高的那个孩子节点的高度值然后加1。
层级:根为 Level 0 层,根的子节点为 Level 1 层,以此类推。
AVL的失衡情况
前面我们介绍了AVL的高度和平衡因子问题,接下来我们来看看有几种情况会导致AVL的失衡,也就是什么情况下我们需要调整AVL树,使其经过调整后能继续维持平衡状态。
对于AVL,需要进行再平衡操作的情况正如以上4个图所示。对应下面4种描述
插入位置 | 如何调整 |
---|---|
插入节点(0006)在发现者(0020)左子树的左边 | LL调整,右单旋 |
插入节点(0014)在发现者(0020)左子树 的右边 | LR调整,左右双旋 |
插入节点(0033)在发现者(0020)右子树 的右边 | RR调整,左单旋 |
插入节点(0025)在发现者(0020)右子树 的左边 | RL调整,右左双旋 |
注:二叉平衡树在插入或删除一个结点时,先检查该操作是否导致了树的不平衡,若是,则在该路径上查找最小的不平衡树(即如果有多个结点同时不满足平衡,就选最靠近下面的那个结点作为最小的不平衡树,称为发现者),调节其平衡。
注2:LL,LR,RR,RL调整仅仅是表示插入结点
相对于发现结点
的位置,而旋转方向,我认为是针对旋转点的位置变化而言的。
对于单旋,旋转点为发现者,左旋:发现者及其左子节点向下移动,右节点向上移动。右旋:发现者及其右子节点向右下沉。同时维护处理其他节点间的关联关系。
对于双旋如LR,是先以发现节点的左节点左旋,再以发现节点右旋。
这里也存在不同的说法,如LL是左单旋,我这里是为了方便自己的理解,其实意义一致。
旋转逻辑用代码展现,如下(后面会贴上完整代码)
int rHeight = height(parent.right);
int lHeight = height(parent.left);
//当添加完一个结点后,如果:(右子树的高度-左子树的高度)>1,左旋转
if (rHeight - lHeight > 1) {
//如果它的右子树的左子树高度大于它的左子树高度,双旋转
if (parent.right != null && height(parent.right.left) > lHeight) {
//先对这个结点的右节点进行右旋转
rightRotate(parent.right);
}
//再对当前结点进行左旋转即可。
leftRotate(parent);
}
//当添加完一个结点后,如果:(左子树的高度-右子树的高度)>1,右旋转
else if (lHeight - rHeight > 1) {
//如果它的左子树的右子树高度大于它的右子树高度,双旋转
if (parent.left != null && height(parent.left.right) > rHeight) {
//先对这个结点的左节点进行左旋转
leftRotate(parent.left);
}
//再对当前结点进行右旋转即可。
rightRotate(parent);
}
算了,不写了,弄明白了,直接贴代码吧。其中Tree的代码在另一篇二叉树的文章中
package tree;
/**
* 平衡二叉树(非递归版)
*/
public class AVLTree<V extends Comparable<? super V>> extends Tree<V> {
@Override
public Node insert(V v) {
Node newNode = super.insert(v);
// 找到最小失衡树
Node parent = newNode;
balance(parent);
return newNode;
}
@Override
public Node remove(V v) {
Node remove = super.remove(v);
balance(remove);
return remove;
}
private void balance(Node parent) {
while (parent != null) {
if (Math.abs(height(parent.left) - height(parent.right)) > 1) {
break;
}
parent = parent.parent;
}
if (parent == null) return;
int rHeight = height(parent.right);
int lHeight = height(parent.left);
//当添加完一个结点后,如果:(右子树的高度-左子树的高度)>1,左旋转
if (rHeight - lHeight > 1) {
//如果它的右子树的左子树高度大于它的左子树高度,双旋转
if (parent.right != null && height(parent.right.left) > lHeight) {
//先对这个结点的右节点进行右旋转
rightRotate(parent.right);
}
//再对当前结点进行左旋转即可。
leftRotate(parent);
}
//当添加完一个结点后,如果:(左子树的高度-右子树的高度)>1,右旋转
else if (lHeight - rHeight > 1) {
//如果它的左子树的右子树高度大于它的右子树高度,双旋转
if (parent.left != null && height(parent.left.right) > rHeight) {
//先对这个结点的左节点进行左旋转
leftRotate(parent.left);
}
//再对当前结点进行右旋转即可。
rightRotate(parent);
}
}
}
再附上测试代码,测试几组不同结构的二叉树:
测试结果验证无误,按自己的逻辑手写的,但是不保证有没有什么奇怪的bug未发现,欢迎指正
package tree;
import java.util.List;
public class Client {
public static void main(String[] args) {
Tree<Integer> tree = new AVLTree<>();
// int[] arr = {5,4,6,3,2};
// int[] arr = {10, 5, 20, 4, 7, 30, 40, 50};
// int[] arr = {10, 5, 20, 2, 6, 1};
// int[] arr = {5, 4, 3,};
// int[] arr = {6, 7, 8};
// int[] arr = {4, 3, 6, 5, 7, 8};
// int[] arr = {7, 4, 8, 3, 5, 6};
// int[] arr = {10, 5, 20, 15, 30};
int[] arr = {60, 10, 70, 5, 20, 15, 30}; // 这组数例子有些意思
//循环添加结点到二叉排序树
for (int tmp : arr) {
tree.insert(tmp);
}
tree.remove(5);
List<Integer> integers = tree.levelOrder(tree.getRoot());
for (int i : integers) {
System.err.println(i);
}
}
}