前言
和二叉搜索树比起来,AVL树(平衡二叉搜索树)才是应用最广泛的。因为二叉搜索树一旦退化成或者趋近于链表,那其存在的意义便消失了。所以,二叉搜索树要在每次增删之后重新恢复平衡,才能在查找的时候保证足够的效率。换句话说,AVL树的查找代价被平摊到每次rebalance上了,因此才会使得AVL树的搜索变得非常高效。
定义
AVL树,其名称来源于其创作者们的首字母缩写,不再赘述。根据维基百科的定义,AVL树有以下关键概念:
节点高度
某个节点所在的高度可以理解为以该节点为根的子树的高度。可以定义叶结点高度为1,其父结点的高度依次+1即可。
平衡因子
一个节点的平衡因子指其右子树与左子树的高度差:
BalanceFactor(N):=Height(RightSubtree(N))–Height(LeftSubtree(N)) B a l a n c e F a c t o r ( N ) := H e i g h t ( R i g h t S u b t r e e ( N ) ) – H e i g h t ( L e f t S u b t r e e ( N ) ) 只有当所有节点的平衡因子在-1、0、1的范围内时,该二叉搜索树才能被称为AVL树,即该二叉搜索树是“平衡的”(
balanced)。当平衡因子小于0时,左子树高于右子树,该节点可被称为“左边偏重”(
left-heavy),反之则为“右边偏重”(
right-heavy)。
二叉树的旋转
在了解AVL树的平衡操作之前,先需要知道二叉树的两种旋转操作及其意义:左旋转、右旋转。
左旋转
假设node节点为需要旋转的节点,当向左旋转时,node成为right节点的左子节点,right节点成为node的父节点,同时如果right存在左子节点,(由于现在right的左子节点被node占了)则此节点成为node的右子节点。
左旋转一共变动了三个指针(图中红色标注。当然,如果节点中定义了指向父节点的指针,这些指针自然也是需要变动的。)
左旋转的意义:使node节点的平衡因子-2。
右旋转
右旋转和左旋转是对称的。节点的变动如图右侧所示。
右旋转的意义:使node节点的平衡因子+2。
失衡类型及其调整
当二叉树上任意一个节点的平衡因子小于-1或大于1时,我们就说二叉树失衡了。
根据AVL失衡的定义,失衡无非两种情况:
1. Left-heavy;
2. Right-heavy.
先看第一种情况。假设失衡的节点为z,当z属于left-heavy时,其左子树(假设根结点为y)比右子树高2。假设右子树高为1,则左子树高为3。这也就是说子树y的高度为3,那么有可能y的左子树高度为2,也有可能y的右子树高度为2,但不可能二者皆为2:
因为AVL树每次失衡之后都会立即调节为平衡树,这也就意味着平衡状态下,在增删某个节点使该树重新变为不平衡之前,子树y的高度最高是2。因此在增删某个节点之后,子树y最多只有一棵子树高度能达到2。
L-L
对于上面的情况,可看到最长的分支在树z的左子树y的左子树上,也被称为Left-Left类型的失衡:
T1, T2, T3 and T4 are subtrees.
z y
/ \ / \ y T4 Right Rotate (z) x z
/ \ - - - - - - - - -> / \ / \
x T3 T1 T2 T3 T4
/ \ T1 T2
这种情况下,z的平衡因子为-2。对于这种失衡,解决方式就是右旋转z节点,刚刚说道,右旋转可使节点平衡因子+2,因此z的平衡因子从-2变为0,重新归为平衡。(这一步只是让节点z平衡了,至于其他的节点,需要逐一去处理)
L-R
同样,left-heavy也有可能是树z的左子树y的右子树过高导致,这种情况被称为Left-Right类型的失衡:
z z x
/ \ / \ / \
y T4 Left Rotate (y) x T4 Right Rotate(z) y z
/ \ - - - - - - - - -> / \ - - - - - - - -> / \ / \ T1 x y T3 T1 T2 T3 T4
/ \ / \ T2 T3 T1 T2
这种情况下,z的平衡因子为-2,但是不能直接右旋转z,因为根据右旋转的方式,旋转过后子树x成为了z的左子树,此时z的高度为3,而y的高度为1,z的平衡因子变为3-1=2,于是我们把一个left-heavy的节点旋转成了right-heavy,依旧是不平衡的。
解决的方法也很简单:先把z的左子树变成L-L的情况,然后再右旋转z即可。所以先左旋转z的左子树的根y,再右旋转z,节点z重归平衡。
以上只是假设使节点z失衡的为left-heavy情况,同理,对于right-heavy情况也有两种:
R-L
树z的右子树y的左子树过高,Right-Left:
z z x
/ \ / \ / \
T1 y Right Rotate (y) T1 x Left Rotate(z) z y
/ \ - - - - - - - - -> / \ - - - - - - - -> / \ / \ x T4 T2 y T1 T2 T3 T4
/ \ / \ T2 T3 T3 T4
z的平衡因子为2,所以先右旋转z的右子树的根y,再左旋转左子树z。
R-R
树z的右子树y的右子树过高,Right-Right:
z y
/ \ / \
T1 y Left Rotate(z) z x
/ \ - - - - - - - -> / \ / \ T2 x T1 T2 T3 T4
/ \ T3 T4
z的平衡因子为2,左旋转可使其平衡因子-2,重归平衡。
总结一下:
– L-L:rotate right;
– R-R:rotate left;
(和名字顺序颠倒,L则右转,R则左转)
– L-R:rotate left, then rotate right;
– R-L:rotate right, then rotate left.
(和名字顺序一样,L-R就是先左后右,R-L就是先右后左)
算法
数据结构
AVL树跟普通二叉搜索树相比,需要额外存储平衡因子(或者说存储树的高度,由此算出平衡因子),因此和二叉搜索树的节点可以是继承关系:
protected class AVLNode<T extends Comparable<T>> extends Node<T> {
protected int height = 1;
/** * Constructor for an AVL node * * @param parent * Parent of the node in the tree, can be NULL. * @param value * Value of the node in the tree. */
protected AVLNode(Node<T> parent, T value) {
super(parent, value);
}
/** * Determines is this node is a leaf (has no children). * * @return True if this node is a leaf. */
protected boolean isLeaf() {
return ((lesser == null) && (greater == null));
}
/** * Updates the height of this node based on it's children. */
protected int updateHeight() {
int lesserHeight = getSubTreeHeight(lesser);
int greaterHeight = getSubTreeHeight(greater);
return height = lesserHeight > greaterHeight ? lesserHeight + 1 : greaterHeight + 1;
}
/** * Get the balance factor(greaterHeight - lesserHeight) for this node. * * @return An integer representing the balance factor for this node. It * will be negative if the lesser branch is longer than the * greater branch. */
protected int getBalanceFactor() {
return getSubTreeHeight(greater) - getSubTreeHeight(lesser);
}
private int getSubTreeHeight(Node<T> subRoot) {
int height = 0;
if (subRoot != null) {
AVLNode<T> subRootAVLNode = (AVLNode<T>) subRoot;
height = subRootAVLNode.height;
}
return height;
}
/** * {@inheritDoc} */
@Override
public String toString() {
return "value=" + id + " height=" + height + " parent=" + ((parent != null) ? parent.id : "NULL")
+ " lesser=" + ((lesser != null) ? lesser.id : "NULL") + " greater="
+ ((greater != null) ? greater.id : "NULL")youbianpianzhong;
}
}
AVL树与普通二叉搜索树在查询上并没有什么区别,在增删上也一样。但是增删过后可能会使二叉搜索树不再平衡,所以每次增删过后都需要对树进行rebalance操作。
增
/** * {@inheritDoc} */
@Override
protected Node<T> addValue(T id) {
Node<T> nodeToReturn = super.addValue(id);
AVLNode<T> nodeAdded = (AVLNode<T>) nodeToReturn;
// this new added node has no children:
// no need to update height
// nodeAdded.updateHeight();
// no need to balance
// balanceAfterInsert(nodeAdded);
nodeAdded = (AVLNode<T>) nodeAdded.parent;
while (nodeAdded != null) {
int h1 = nodeAdded.height;
nodeAdded.updateHeight();
balanceAfterInsert(nodeAdded);
// If height before and after balance is the same, stop going up the tree
int h2 = nodeAdded.height;
if (h1==h2)
break;
nodeAdded = (AVLNode<T>) nodeAdded.parent;
}
return nodeToReturn;
}
增加节点之后,该节点的父节点也许不再平衡,因此对其父节点递归进行rebalance操作。
考虑一种情况:如果对某节点rebalance前后,该节点高度并没有变化,则该节点的rebalance不会影响其父节点的平衡性,那么rebalance过程可以就此终止,无需一直递归到root节点。
rebalance after add
具体的rebalance操作:
/** * Balance the tree according to the AVL post-insert algorithm. * * @param node * Root of tree to balance. */
private void balanceAfterInsert(AVLNode<T> node) {
int balanceFactor = node.getBalanceFactor();
// need to balance
if (balanceFactor > 1 || balanceFactor < -1) {
AVLNode<T> child = null;
Balance balance = null;
// left-heavy
if (balanceFactor < 0) {
child = (AVLNode<T>) node.lesser;
balanceFactor = child.getBalanceFactor();
// left-heavy
if (balanceFactor < 0) {
// left-left(-heavy)
balance = Balance.LEFT_LEFT;
}
else {
// left-right(-heavy)
balance = Balance.LEFT_RIGHT;
}
// right-heavy
} else {
child = (AVLNode<T>) node.greater;
balanceFactor = child.getBalanceFactor();
// left-heavy
if (balanceFactor < 0) {
// right-left(-heavy)
balance = Balance.RIGHT_LEFT;
}
// right-heavy
else {
// right-right(-heavy)
balance = Balance.RIGHT_RIGHT;
}
}
if (balance == Balance.LEFT_RIGHT) {
// Left-Right (Left rotation, right rotation)
rotateLeft(child);
rotateRight(node);
} else if (balance == Balance.RIGHT_LEFT) {
// Right-Left (Right rotation, left rotation)
rotateRight(child);
rotateLeft(node);
} else if (balance == Balance.LEFT_LEFT) {
// Left-Left (Right rotation)
rotateRight(node);
} else {
// Right-Right (Left rotation)
rotateLeft(node);
}
child.updateHeight();
node.updateHeight();
}
}
具体的rebalance过程就是:
1. 判断该node是否失衡;
2. 如果失衡,属于哪种失衡(LL/LR/RL/RR);
3. 对于每一种失衡,进行相应的旋转操作即可。
rotate left
/** * Rotate tree left at sub-tree rooted at node. * * @param node * Root of tree to rotate left. */
protected void rotateLeft(Node<T> node) {
Node<T> parent = node.parent;
Node<T> greater = node.greater;
Node<T> lesser = greater.lesser;
greater.lesser = node;
node.parent = greater;
node.greater = lesser;
if (lesser != null)
lesser.parent = node;
if (parent!=null) {
if (node == parent.lesser) {
parent.lesser = greater;
} else if (node == parent.greater) {
parent.greater = greater;
} else {
throw new RuntimeException("Yikes! I'm not related to my parent. " + node.toString());
}
greater.parent = parent;
} else {
root = greater;
root.parent = null;
}
}
具体的旋转,无非就是按照之前的图示,调整几个指针(红色箭头)的指向。由于我们给AVL节点增加了parent指针,所以当节点变动的时候,同时需要调整其parent节点的指向。
rotate right
/** * Rotate tree right at sub-tree rooted at node. * * @param node * Root of tree to rotate right. */
protected void rotateRight(Node<T> node) {
Node<T> parent = node.parent;
Node<T> lesser = node.lesser;
Node<T> greater = lesser.greater;
lesser.greater = node;
node.parent = lesser;
node.lesser = greater;
if (greater != null)
greater.parent = node;
if (parent!=null) {
if (node == parent.lesser) {
parent.lesser = lesser;
} else if (node == parent.greater) {
parent.greater = lesser;
} else {
throw new RuntimeException("Yikes! I'm not related to my parent. " + node.toString());
}
lesser.parent = parent;
} else {
root = lesser;
root.parent = null;
}
}
右旋转同理。
删
/** * {@inheritDoc} */
@Override
protected Node<T> removeValue(T value) {
// Find node to remove
Node<T> nodeToRemoved = this.getNode(value);
if (nodeToRemoved==null)
return null;
// Find the replacement node
Node<T> replacementNode = this.getReplacementNode(nodeToRemoved);
// Find the parent of the replacement node to re-factor the height/balance of the tree
AVLNode<T> nodeToRefactor = null;
if (replacementNode != null)
nodeToRefactor = (AVLNode<T>) replacementNode.parent;
if (nodeToRefactor == null)
nodeToRefactor = (AVLNode<T>) nodeToRemoved.parent;
if (nodeToRefactor != null && nodeToRefactor == nodeToRemoved)
nodeToRefactor = (AVLNode<T>) replacementNode;
// Replace the node
replaceNodeWithNode(nodeToRemoved, replacementNode);
// Re-balance the tree all the way up the tree
while (nodeToRefactor != null) {
nodeToRefactor.updateHeight();
balanceAfterDelete(nodeToRefactor);
nodeToRefactor = (AVLNode<T>) nodeToRefactor.parent;
}
return nodeToRemoved;
}
删节点同二叉搜索树中的操作。删后需要做rebalance,和增加节点一样,一直循环对该节点的父节点做rebalance操作即可。
rebalance after delete
/** * Balance the tree according to the AVL post-delete algorithm. * * @param node * Root of tree to balance. */
private void balanceAfterDelete(AVLNode<T> node) {
int balanceFactor = node.getBalanceFactor();
if (balanceFactor == -2 || balanceFactor == 2) {
if (balanceFactor == -2) {
AVLNode<T> ll = (AVLNode<T>) node.lesser.lesser;
int lesser = (ll != null) ? ll.height : 0;
AVLNode<T> lr = (AVLNode<T>) node.lesser.greater;
int greater = (lr != null) ? lr.height : 0;
if (lesser >= greater) {
rotateRight(node);
node.updateHeight();
if (node.parent != null)
((AVLNode<T>) node.parent).updateHeight();
} else {
rotateLeft(node.lesser);
rotateRight(node);
AVLNode<T> p = (AVLNode<T>) node.parent;
if (p.lesser != null)
((AVLNode<T>) p.lesser).updateHeight();
if (p.greater != null)
((AVLNode<T>) p.greater).updateHeight();
p.updateHeight();
}
} else if (balanceFactor == 2) {
AVLNode<T> rr = (AVLNode<T>) node.greater.greater;
int greater = (rr != null) ? rr.height : 0;
AVLNode<T> rl = (AVLNode<T>) node.greater.lesser;
int lesser = (rl != null) ? rl.height : 0;
if (greater >= lesser) {
rotateLeft(node);
node.updateHeight();
if (node.parent != null)
((AVLNode<T>) node.parent).updateHeight();
} else {
rotateRight(node.greater);
rotateLeft(node);
AVLNode<T> p = (AVLNode<T>) node.parent;
if (p.lesser != null)
((AVLNode<T>) p.lesser).updateHeight();
if (p.greater != null)
((AVLNode<T>) p.greater).updateHeight();
p.updateHeight();
}
}
}
}
同样也是先判断失衡类型(LL/LR/RL/RR),然后进行相应的旋转操作。
总结
AVL树才是最常用的二叉搜索树。其存在的意义显而易见,关键是要知道和rebalance相关的旋转操作是怎么进行的,以及旋转能达到什么效果。
参阅
对以下内容作者深表感谢:
1. https://en.wikipedia.org/wiki/AVL_tree
2. https://www.geeksforgeeks.org/avl-tree-set-1-insertion/
3. https://github.com/puppylpg/java-algorithms-implementation/blob/master/src/com/jwetherell/algorithms/data_structures/AVLTree.java