平衡二叉树(AVL Tree)
概述
AVL树是以二分搜索树(BST)为底层数据结构而实现的,其特性是需要维护AVL的|平衡因子| <= 1
平衡因子
对于一个父节点的左右子树高度差的绝对值需要 <= 1
AVL最复杂的操作在于add()
和remove()
两个方法需要维护AVL的平衡二叉树特性,细节在下面展开
节点类
由于需要知道AVL一个节点的高度,因此需要在BST的内部类Node
中新维护一个成员变量height
public class Node<K, V> {
K key;
V value;
int height;
Node<K, V> left, right;
public Node(K key, V value) {
this.key = key;
this.value = value;
this.left = null;
this.right = null;
this.height = 1;
}
}
IMap接口
该接口定义了BST的方法
public interface IMap<K, V> {
void add(K key, V value);
V remove(K key);
boolean contains(K key);
V get(K key);
void set(K key, V value);
int getSize();
boolean isEmpty();
}
AVLTree类
成员变量以及构造方法
private int size;
private Node<K, V> root;
public AVLTree() {
root = null;
size = 0;
}
基本的方法
下面的方法均是为了实现add()
和remove()
方法时,可能造成AVL的平衡性被打破而封装的辅助方法
- getHeight()
private int getHeight(Node<K, V> node) {
if (node == null) {
return 0;
}
return node.height;
}
该方法用于获取某节点的高度,且维护node
为null
时可能抛出的异常
- getNode(Node node, K key)
private Node<K, V> getNode(Node<K, V> node, K key) {
if (root == null) {
return null;
}
if (key.compareTo(node.key) == 0) {
return node;
} else if (key.compareTo(node.key) < 0) {
return getNode(node.left, key);
} else {
return getNode(node.right, key);
}
}
根据key获取节点
- getBalanceFactor(Node node)
// 获取平衡因子
private int getBalanceFactor(Node<K, V> node) {
if (node == null) {
return 0;
}
return getHeight(node.left) - getHeight(node.right);
}
获取平衡因子,并且维护了node
可能为null
而抛出异常的情况
核心方法
- add(Node node)
@Override
public void add(K key, V value) {
root = add(root, key, value);
}
private Node<K, V> add(Node<K, V> node, K key, V value) {
if (node == null) {
size++;
return new Node<>(key, value);
}
if (key.compareTo(node.key) < 0) {
node.left = add(node.left, key, value);
} else if (key.compareTo(node.key) > 0) {
node.right = add(node.right, key, value);
} else {
// 如果插入节点存在,此处定义为改变 Value 的值(也可以根据情况不做改变)
node.value = value;
}
node.height = Math.max(getHeight(node.left), getHeight(node.right) + 1);
int balanceFactor = getBalanceFactor(node);
// 平衡维护
// LL
if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0) {
return rightRotate(node);
}
// RR
if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0) {
return leftRotate(node);
}
// LR
if (balanceFactor > 1 && getBalanceFactor(node.right) < 0) {
node.left = leftRotate(node.left);
return rightRotate(node);
}
// RL
if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
node.right = leftRotate(node);
return rightRotate(node.right);
}
return node;
}
- remove(K key)
@Override
public V remove(K key) {
Node<K, V> node = getNode(root, key);
if (node != null) {
root = remove(root, key);
return node.value;
}
return null;
}
private Node<K, V> remove(Node<K, V> node, K key) {
if (node == null) {
return null;
}
Node<K, V> retNode;
if (key.compareTo(node.key) < 0) {
node.left = remove(node.left, key);
retNode = node;
} else if (key.compareTo(node.key) > 0) {
node.right = remove(node.right, key);
retNode = node;
} else {
if (node.left == null) {
Node<K, V> rightNode = node.right;
node.right = null;
size--;
retNode = node;
}
if (node.right == null) {
Node<K, V> leftNode = node.left;
node.left = null;
size--;
retNode = node;
}
// 左右子树均不为空的情况
Node<K, V> minimum = minimum(node.right);
minimum.right = removeMin(node.right);
minimum.left = node.left;
node.left = node.right = null;
retNode = minimum;
}
retNode.height = 1 + Math.max(getHeight(retNode.left), getHeight(retNode.right));
int balanceFactor = getBalanceFactor(retNode);
// 平衡维护
// LL
if (balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0) {
return rightRotate(retNode);
}
// RR
if (balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0) {
return leftRotate(retNode);
}
// LR
if (balanceFactor > 1 && getBalanceFactor(retNode.right) < 0) {
retNode.left = leftRotate(retNode.left);
return rightRotate(retNode);
}
// RL
if (balanceFactor < -1 && getBalanceFactor(retNode.right) > 0) {
retNode.right = leftRotate(retNode);
return rightRotate(retNode.right);
}
return retNode;
}
维护AVL的平衡性质——旋转
这里是文章的核心内容,即如何保证AVL的平衡性质,这里开始详细描述维护AVL特性的机制——旋转
旋转分为四种形式,分别为:
- LL旋转
- RR旋转
- LR旋转
- RL旋转
只需要理解一种旋转机制,就可以理解其它的旋转机制
这里以LL和LR旋转为例
LL旋转
- 如图所示,根节点的
平衡因子
为2,而破坏该AVL的节点在根节点的左子树的左节点,因此需要LL旋转来维护其平衡
- 接下来根据实际代码进一步理解这个机制
- 为了结合代码,现在虚拟出三个节点(或子树),它们可以是
null
,也可以实际存在
- 目前由于是LL旋转,那么提供一个private方法用于维护平衡,下面给出方法详细代码,代码后有每一个表达式的详细解释
// 右旋转
private Node<K, V> rightRotate(Node<K, V> y) {
Node<K, V> x = y.left;
Node<K, V> t3 = x.right;
x.right = y;
y.left = t3;
// 更新height
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
return x;
}
- 第一行表达式
Node<K, V> x = y.left
,将发现节点y
的左子树暂存
- 第二行表达式
Node<K, V> t3 = x.right
,将单独拿出的节点的右子树t3
暂存
- 第三行表达式
x.right = y
,将发现节点y
接入x
的右子树
- 第四行表达式
y.left = t3
,将暂存节点t3
插入发现节点y
的左节点
- 注意:以上的每个操作都是满足了二分搜索树(BST)的特性,这点对于理解旋转机制很重要
- 由于最终平衡后的AVL高度发生了改变,因此需要对更新后的AVL高度进行维护
// 更新height
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
LR旋转
LR旋转和LL旋转的差别在于LR旋转需要进行两次旋转操作,具体步骤如下:
- 首先对于上面的AVL进行一次
左旋转
,然后就会暂时将其转变为LL旋转的状态
- 然后再进行一次
右旋转
,即可恢复到AVL的状态
易错点分析
AVL有一个非常容易误解的地方,如图
实际上旋转的命名(LL、RR、LR、RL)也都是根据破坏节点t1(t2)
与发现节点y
的相对位置命名
也就是说无论是:
还是:
旋转方式是相同的,均为LL旋转,结果如图: