Balanced Binary Search Tree Tree
经典常见的平衡二叉搜索树
AVL树
- 平衡因子:某节点的左右子树高度差
- AVL的特点
- 每个节点的平衡因子只可能是 1、0、-1(绝对值 ≤ 1,如果超过 1,称之为“失衡”)
- 每个节点的左右子树高度差不超过 1
- 搜索、添加、删除的时间复杂度是 O(logn)
情况与解决办法有4种
- LL,即左子树的左子树,需要使用右旋转,也就是说将中间节点作为这三个节点的根节点
- RR,使用左旋转
- LR,即左子树的右子树,先变换为LL的情况,即左旋转将n节点作为第二个中间节点可以得到,再使用第一种的LL右旋转
- RL,先变换为RR的情况,即右旋转可以得到,再使用第二种的RR左旋转
1.LL的情况,右旋转
![在这里插入图片描述](https://img-blog.csdnimg.cn/5f622926cb5d418d95a68fa247d5eaef.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASmF2YeWwj-eZveeahOeglOeptuWDpw==,size_20,color_FFFFFF,t_70,g_se,x_16)
2.RR的情况,左旋转
![在这里插入图片描述](https://img-blog.csdnimg.cn/795afa663b9143288db730e937b13a9a.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASmF2YeWwj-eZveeahOeglOeptuWDpw==,size_20,color_FFFFFF,t_70,g_se,x_16)
3.LR的情况,先左旋转变成LL,再右旋转
![在这里插入图片描述](https://img-blog.csdnimg.cn/8ac1fd1e09b74e5bb31efa862f1d8c04.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASmF2YeWwj-eZveeahOeglOeptuWDpw==,size_20,color_FFFFFF,t_70,g_se,x_16)
4.RL的情况,先右旋转变成RR,再左旋转
![在这里插入图片描述](https://img-blog.csdnimg.cn/6fe7cbc979c74b51b8e0a50737833da8.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASmF2YeWwj-eZveeahOeglOeptuWDpw==,size_20,color_FFFFFF,t_70,g_se,x_16)
添加导致的失衡
- 通过新添加的节点向上找parent.parent…找到最低的”失衡“节点,让它恢复平衡,整个树的高度也就平衡了(因为每一次的添加都进行平衡转换),其他父节点也就平衡了(因为导致失衡的节点就是祖先节点grandparent,即node、parent、grandparent这三代)
- 也存在一直添加都没有失衡情况发生,所以到其parent等于空时候停
- 然后判断是否平衡,即看平衡因子,如果平衡,则更新高度,否则恢复平衡
@Override
protected void afterAdd(Node<E> node) {
while ((node = node.parent) != null) {
if (isBalanced(node)) {
((AVLNode<E>)node).updateHeight();
} else {
rebalance(node);
break;
}
}
}
private boolean isBalanced(Node<E> node) {
return Math.abs(((AVLNode<E>)node).balanceFactor()) <= 1;
}
- 由于任意一个二叉树不用存height属性,因为浪费内存,所以不可在Node类加入height属性,那么在AVL类内新添加一个类继承于Node,添加属性height
- 一开始求高度有一种方法是递归,左右子节点较高的加1,效率太低。所以在此在while循环中就做了更新高度(在while循环中顺便更新了高度,提高性能),即如果平衡了就更新高度,否则恢复平衡
private static class AVLNode<E> extends Node<E> {
int height = 1;
public AVLNode(E element, Node<E> parent) {
super(element, parent);
}
public int balanceFactor() {
int leftHeight = left == null ? 0 : ((AVLNode<E>)left).height;
int rightHeight = right == null ? 0 : ((AVLNode<E>)right).height;
return leftHeight - rightHeight;
}
public void updateHeight() {
int leftHeight = left == null ? 0 : ((AVLNode<E>)left).height;
int rightHeight = right == null ? 0 : ((AVLNode<E>)right).height;
height = 1 + Math.max(leftHeight, rightHeight);
}
public Node<E> tallerChild() {
int leftHeight = left == null ? 0 : ((AVLNode<E>)left).height;
int rightHeight = right == null ? 0 : ((AVLNode<E>)right).height;
if (leftHeight > rightHeight) return left;
if (leftHeight < rightHeight) return right;
return isLeftChild() ? left : right;
}
public boolean isLeftChild() {
return parent != null && this == parent.left;
}
- 由于在实现过程中抽象出二叉树,AVL树继承二叉搜索树,而在二叉搜索树的添加方法里面,是创建一个Node节点对象,而AVL树添加节点的时候需要创建其AVLNode节点(因为包含height属性),所以在BinaryTree(二叉树类)中添加一个创建节点的方法createNode()供BST(二叉搜索树)调用,然后其添加代码由创建一个新节点改变为调用创建节点的方法实现,进而AVL树中可以重写createNode()方法,进而实现代码重用
class BinartTree{
...
protected Node<E> createNode(E element, Node<E> parent) {
return new Node<>(element, parent);
}
...
}
root = createNode(element, null);
Node<E> newNode = createNode(element, parent);
afterAdd(root);
@Override
protected Node<E> createNode(E element, Node<E> parent) {
return new AVLNode<>(element, parent);
}
- 恢复平衡:有四种方法LL、RR、LR、RL,由于传入的节点是高度最低的那个不平衡节点,所以需要找其孩子parent和孙子节点node。看上面四个图可以看到:其实parent是grand的左右子树较高的那个,node是parent左右子树高度较高的那一个
private void rebalance(Node<E> grand) {
Node<E> parent = ((AVLNode<E>)grand).tallerChild();
Node<E> node = ((AVLNode<E>)parent).tallerChild();
if (parent.isLeftChild()) {
if (node.isLeftChild()) {
rotate(grand, node, node.right, parent, parent.right, grand);
} else {
rotate(grand, parent, node.left, node, node.right, grand);
}
} else {
if (node.isLeftChild()) {
rotate(grand, grand, node.left, node, node.right, parent);
} else {
rotate(grand, grand, parent.left, parent, node.left, node);
}
}
}
- 左旋转、右旋转rotateLeft(grand) 和 rotateRight(grand)
1.更新左右left和right属性
2.更新父节点parent属性
3.更新grand和parent的高度
private void rotateLeft(Node<E> grand) {
Node<E> parent = grand.right;
Node<E> child = parent.left;
grand.right = child;
parent.left = grand;
afterRotate(grand, parent, child);
}
private void rotateRight(Node<E> grand) {
Node<E> parent = grand.left;
Node<E> child = parent.right;
grand.left = child;
parent.right = grand;
afterRotate(grand, parent, child);
}
private void afterRotate(Node<E> grand, Node<E> parent, Node<E> child) {
parent.parent = grand.parent;
if (grand.isLeftChild()) {
grand.parent.left = parent;
} else if (grand.isRightChild()) {
grand.parent.right = parent;
} else {
root = parent;
}
if (child != null) {
child.parent = grand;
}
grand.parent = parent;
updateHeight(grand);
updateHeight(parent);
}
统一旋转操作
- 如下图,我们可以发现无论是LL、LR、RR、RL都有a < b < c < d < e < f < g,并且最终让d沉稳给根节点,而a仍是b的左节点、g仍是f的右节点。
- 所以传入b、c、d、e、f及原树的根节点即可
private void rotate(
Node<E> r,
Node<E> b, Node<E> c,
Node<E> d,
Node<E> e, Node<E> f) {
d.parent = r.parent;
if (r.isLeftChild()) {
r.parent.left = d;
} else if (r.isRightChild()) {
r.parent.right = d;
} else {
root = d;
}
b.right = c;
if (c != null) {
c.parent = b;
}
updateHeight(b);
f.left = e;
if (e != null) {
e.parent = f;
}
updateHeight(f);
d.left = b;
d.right = f;
b.parent = d;
f.parent = d;
updateHeight(d);
}
删除导致的失衡
- 可能会导致 父节点 或祖先节点 失衡(只有 1个节点会失衡)
- 恢复平衡 后,可能会导致更高层的祖先节点失【最多需要 O(logn) 次调整 】
protected void afterRemove(Node<E> node) {
while ((node = node.parent) != null) {
if (isBalanced(node)) {
((AVLNode<E>)node).updateHeight();
} else {
rebalance(node);
}
}
}
总结
- 添加----afterAdd()----添加之后的处理
可能会导致 所有 祖先节点 都失衡
只要让高度最低的失衡节点恢复平衡,整棵树就【仅需 O(1) 次调整 】 - 删除----afterRemove()—删除之后的处理
可能会导致 父节点 或祖先节点 失衡(只有 1个节点会失衡)
恢复平衡 后,可能会导致更高层的祖先节点失【最多需要 O(logn) 次调整 】 - 平均时间复杂度
搜索: O(logn)
添加: O(logn) logn),仅需 O(1) 次的旋转操作
删除: O(logn) logn),最多需要 O(logn)次的旋转操作