前面我们已经介绍了二叉查找树的性质以及二叉查找树的实现,这篇博客我们介绍带平衡的二叉查找树。在介绍之前,必须纠正一个错误。
树的高度: 对任意结点ni, ni的高度为从ni到一片树叶的最长路径的长。如:叶子结点的高度为0
树的深度: 对任意结点ni, ni的深度为从根到ni的唯一的路径的长。如:根结点的深度为0
很多人会将这两个概念混淆,甚至于我看到的一本电子书上的作者也将这个概念混淆了。
AVL 带平衡的二叉查找树
AVL是在二叉查找树的基础上升级的一种树结构,AVL除了具有二叉查找树的特性外,还有它独特的性质,即其每个结点的左子树和右子树高度最多差1 ,如下图所示为AVL树,其中红色的为各个结点的高度:
上图中如果将14结点删除则不是AVL树。在这颗树中,如果我们插入一个结点0,这就破坏了原有树的平衡,需要通过旋转来进行修正。而插入的情况分为以下四种:
- 对结点的左儿子的左子树进行一次插入 (左左单旋转)
- 对结点的左儿子的右子树进行一次插入 (左右双旋转)
- 对结点的右儿子的左子树进行一次插入 (右左单旋转)
- 对结点的右儿子的右子树进行一次插入 (右右单旋转)
如: 我们需要向上图中插入结点0,原有树的平衡被打破,结点5和结点12的高度相差2.
上图中的树如果想要保持平衡,需要进行一次左左单旋转,将结点3和结点5进行一次左左单旋转,得到的结果如下图所示(右右单旋转和左左单旋转是关于结点的镜像对称,这里不再举例):
下面给出一棵新的树:
对上图进行改进插入一个结点5,树的平衡条件再次发生改变。如下图:
此时我们需要进行一次右左双旋转(发生在结点3、结点6和结点4),而右左双旋转又可以分解为一次左左单旋转和一次右右单旋转。下图是进行左左单旋转(结点6和结点4)后的结果.
进行左左单旋转后我们可以看到树还是没有平衡,此时我们还需要进行一次右右单旋转(发生在结点3和结点4)。旋转后的结果如下图所示
此时我们可以发现树又重新平衡了,类似于右左双旋转,左右双旋转可以分解为一次右右单旋转和一次左左单旋转,这里不再举例。下面给出AVL树的实现
AVL树的实现
UnderflowException.java
public class UnderflowException extends Exception {
public UnderflowException(String message) {
super(message);
}
}
AVLTree.java
import java.util.Comparator;
import java.util.Objects;
public class AVLTree<T extends Comparable<? super T>> {
private static final int ALLOWED_IMBALANCE = 1;
private AvlNode<T> root;
private Comparator<? super T> cmp; // 自定义排序
public AVLTree() {
this(null);
}
public AVLTree(Comparator<? super T> comparator) { // 向下限定
root = null;
this.cmp = comparator;
}
public void makeEmpty() {
root = null;
}
public boolean isEmpty() {
return root == null;
}
public boolean contains(T element) {
return contains(element, root);
}
private boolean contains(T element, AvlNode<T> t) {
if(t == null) return false;
int compareResult = myCompare(element, t.element);
if(compareResult > 0) {
return contains(element,t.left);
}else if(compareResult < 0) {
return contains(element, t.right);
}
return true; // 匹配到了
}
public T findMin() throws UnderflowException {
if(isEmpty()) throw new UnderflowException("树为空!");
return findMin(root).element;
}
private AvlNode<T> findMin(AvlNode<T> t){ // 最小值在最左边
if( t == null ) return null;
else if(t.left == null) return t;
return findMin(t.left);
}
public T findMax() throws UnderflowException {
if(isEmpty()) throw new UnderflowException("树为空!");
return findMax(root).element;
}
private AvlNode<T> findMax(AvlNode<T> t){ // 最大值在最右边
if(t == null) return null;
else if(t.right == null) return t;
return findMax(t.right);
}
public void remove(T element) {
root = remove(element,root);
}
private AvlNode<T> remove(T element, AvlNode<T> t){
if( t == null) return null;
int compareResult = myCompare(element, t.element);
if(compareResult > 0) {
return remove(element , t.left);
}else if(compareResult < 0) {
return remove(element , t.right);
}else if(t.left != null && t.right != null) { // 判断是否有两个孩子
t.element = findMin(t.right).element;
t.right = remove(element,t.right);
}else { // 没有孩子或者只有一个孩子的情况
t = (t.left != null) ? t.left:t.right;
}
return balance(t); // 删除完后需要旋转,因为删除可能打破了平衡
}
public void insert(T element) {
root = insert(element, root);
}
/*
* 插入的情况有以下四种:
* 1. 根节点的左子树的左边插入(即左左,单旋转)
* 2. 根结点的左子树的右边插入(即左右,双旋转)
* 3. 根节点的右子树的左边插入(即右左,双旋转)
* 4. 根结点的右子树的右边插入(即右右,单旋转)
*/
private AvlNode<T> insert(T element, AvlNode<T> t) { // 插入
if (t == null) {
return new AvlNode<>(element, null, null);
}
int compareResult = myCompare(element, t.element);
if (compareResult < 0)
t.left = insert(element, t.left);
else if (compareResult > 0)
t.right = insert(element, t.right);
else
;
return balance(t);
}
/*
* 在进行树的平衡时需要考虑以下几点
* 1. 树是否需要旋转
* 2. 如果要旋转,是发生在根结点的左边还是右边
* 3. 是单旋转还是双旋转
*/
private AvlNode<T> balance(AvlNode<T> t) {
if (t == null) // 基线条件
return t;
if (height(t.left) - height(t.right) > ALLOWED_IMBALANCE) // 判断旋转是否发生在左边
if (height(t.left.left) >= height(t.left.right)) // 判断是否是左左单旋转
t = rotateWithLeftChild(t); // 进行单旋转
else
t = doubleWithLeftChild(t); // 进行双旋转
else if (height(t.right) - height(t.left) > ALLOWED_IMBALANCE) // 判断旋转是否发生在右边
if (height(t.right.right) >= height(t.right.left)) // 判断是否为右右单旋转
t = rotateWithRightChild(t); // 进行单旋转
else
t = doubleWithRightChild(t); // 进行双旋转
// 更新高度,max中的到的是子树的高度,t的高度需要在子树的高度上面加1
t.height = Math.max(height(t.left), height(t.right)) + 1;
return t;
}
/*
* 左左单旋转 获取k2右结点k1 将k1的右结点作为k2的左结点 将k2作为k1的结点 更新高度 返回新的根节点k1
*/
private AvlNode<T> rotateWithLeftChild(AvlNode<T> k2) { // 左左单旋转
AvlNode<T> k1 = k2.left;
k2.left = k1.right; // 将k1的右子树变为k2的左子树
k1.right = k2; // 将k2变为k1的右子树
k2.height = Math.max(height(k2.left), height(k2.right)); // 更新k2的高度
k1.height = Math.max(height(k1.left), height(k1.right)); // 更新k1的高度
return k1;
}
/*
* 右右单旋转 获取k1的右结点k2 将k2的左结点作为k1的右结点 将k1作为k2的左结点 更新高度 返回新的根节点k2
*/
private AvlNode<T> rotateWithRightChild(AvlNode<T> k1) {
AvlNode<T> k2 = k1.right;
k1.right = k2.left;
k2.right = k1;
k1.height = Math.max(height(k1.left), height(k1.right));
k2.height = Math.max(height(k2.left), height(k2.right));
return k2;
}
/*
* 左右双螺旋 k3的左结点先进行右右单旋转 k3与k3的左结点进行左左单旋转
*/
private AvlNode<T> doubleWithLeftChild(AvlNode<T> k3) {
k3.left = rotateWithRightChild(k3.left);
return rotateWithLeftChild(k3);
}
/*
* 右左双螺旋 k3的右结点先进行左左单旋转 k3与k3的右结点进行右右单旋转
*/
private AvlNode<T> doubleWithRightChild(AvlNode<T> k1) {
k1.right = rotateWithLeftChild(k1.right);
return rotateWithRightChild(k1);
}
public void printTree() {
printTree(root);
}
private void printTree(AvlNode t) { // 前序遍历
if(t != null) {
printTree(t.left);
System.out.println(t);
printTree(t.right);
}
}
private int height(AvlNode<T> t) { // 返回指定树的高度
return t == null ? -1 : t.height;
}
private int myCompare(T element1, T element2) { // 比较
Objects.requireNonNull(element1, "不能为空!");
Objects.requireNonNull(element2, "不能为空!");
if(cmp != null)
return cmp.compare(element1, element2); // 自定义排序
else
return element1.compareTo(element2); // 自然排序
}
/*
* 二叉平衡树的结点
*/
private static class AvlNode<T> {
T element;
AvlNode<T> left;
AvlNode<T> right;
int height;
AvlNode(T element) {
this(element, null, null);
}
AvlNode(T element, AvlNode<T> lt, AvlNode<T> rt) {
this.element = element;
}
}
}