本文来自:https://blog.zhenlanghuo.top/2017/08/22/AVL%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%AE%9E%E7%8E%B0/
普通的二叉查找树在插入有序的数据的时候会退化为链表,查找的时间复杂度退化为O(n)。而平衡二叉树在插入数据的时候一直保持二叉树的平衡,从而保证查找的时间复杂度维持在O(logn)。
平衡二叉树的定义
一棵平衡二叉树是其每个结点的左子树和右子树的高度最多相差1的二叉查找树(空树的高度为-1)。
二叉树的高度——当前结点到叶子结点的最长路径
四种旋转的情况
若平衡二叉树种某个结点的左子树和右子树的高度相差大于1,该树就是失衡了,该结点称为失衡点,就要通过旋转来保持二叉树的平衡。
一共分四种情况导致结点失衡:
- 在结点的左孩子的左子树中插入数据(LL)
- 在结点的左孩子的右子树中插入数据(LR)
- 在结点的右孩子的左子树中插入数据(RL)
- 在结点的右孩子的右子树中插入数据(RR)
第1和4种情况是对称的,可用单旋来解决,而第2和3种情况也是对称的,要用双旋来解决。
LL型(左孩子的左子树)通过右旋解决
对于LL型的情况,要使用右旋来解决,将失衡点右旋到其左孩子的右孩子的位置,失衡点的左子树更新为其原来左孩子的右子树。
RR型(右孩子的右子树)通过左旋解决
对于RR型的情况,要使用左旋来解决,将失衡点左旋到其右孩子的左孩子的位置,失衡点的右子树更新为其原来右孩子的左子树。
LR型(左孩子的右子树)通过先左旋再右旋解决
对于LR型的情况,要使用先对失衡点的左孩子进行左旋,然后再对失衡点进行右旋来解决。
RL型(右孩子的左子树)通过先右旋再左旋解决
对于RL型的情况,要使用先对失衡点的右孩子进行右旋,然后再对失衡点进行左旋来解决。
3、代码实现
3.1 AVLNode
public class AVLNode<T extends Comparable<T>> {
public AVLNode<T> left;
public AVLNode<T> right;
public T data;
//当前结点的高度
public int height;
public AVLNode(T data) {
this.data = data;
}
}
AVLNode中一定要保存结点的高度,并在高度有变化的时候进行更新,我看过有一些简单的实现,在结点的数据结构中没有记录结点的高度,每次判断是否平衡的时候都要重新递归计算结点的高度,这样的做法效率很低
3.2 四种情况的旋转操作
/**
* 右旋操作(针对LL型的情况)
* @param unbalance
* @return
*/
public AVLNode<T> singleRotateRight(AVLNode<T> unbalance) {
//失衡点的左孩子
AVLNode<T> leftNode = unbalance.left;
//将失衡点的左孩子更新为leftNode的右子树
unbalance.left = leftNode.right;
//失衡点右旋,变成leftNode的右孩子
leftNode.right = unbalance;
//更新leftNode和失衡点的高度
unbalance.height = Math.max(height(unbalance.left),height(unbalance.right)) + 1;
leftNode.height = Math.max(height(leftNode.left),unbalance.height) + 1;
return leftNode;
}
/**
* 左旋操作(针对RR型的情况)
* @param unbalance
* @return
*/
public AVLNode<T> singleRotateLeft(AVLNode<T> unbalance) {
//失衡点的右孩子
AVLNode<T> rightNode = unbalance.right;
//将失衡点的右孩子更新为rightNode的左子树
unbalance.right = rightNode.left;
//失衡点左旋,变成rightNode的左孩子
rightNode.