历史
AVL 树是一种自平衡二叉搜索树,由托尔·哈斯特罗姆在 1960 年提出并在 1962 年发表。它的名字来源于发明者的名字:Adelson-Velsky 和 Landis,他们是苏联数学家,于 1962 年发表了一篇论文,详细介绍了 AVL 树的概念和性质。
概述
在二叉搜索树中,如果插入的元素按照特定的顺序排列,可能会导致树变得非常不平衡,从而降低搜索、插入和删除的效率。为了解决这个问题,AVL 树通过在每个节点中维护一个平衡因子来确保树的平衡。平衡因子是左子树的高度减去右子树的高度。如果平衡因子的绝对值大于等于 2,则通过旋转操作来重新平衡树。
AVL 树是用于存储有序数据的一种重要数据结构,它是二叉搜索树的一种改进和扩展。它不仅能够提高搜索、插入和删除操作的效率,而且还能够确保树的深度始终保持在 O(log n) 的水平。随着计算机技术的不断发展,AVL 树已经成为了许多高效算法和系统中必不可少的一种基础数据结构。
如果一棵二叉搜索树长的不平衡,那么查询的效率会受到影响,如下图
通过旋转可以让树重新变得平衡,并且不会改变二叉搜索树的性质(即左边仍然小,右边仍然大)
如何判断失衡?
如果一个节点的左右孩子,高度差超过 1,则此节点失衡,才需要旋转
失衡的四种情况
LL
- 失衡节点(图中 8 红色)的 bf > 1,即左边更高
- 失衡节点的左孩子(图中 6)的 bf >= 0 即左孩子这边也是左边更高或等高
LR
- 失衡节点(图中 8)的 bf > 1,即左边更高
- 失衡节点的左孩子(图中 3 红色)的 bf < 0 即左孩子这边是右边更高
RL
- 失衡节点(图中 3)的 bf <-1,即右边更高
- 失衡节点的右孩子(图中 6 红色)的 bf > 0,即右孩子这边左边更高
RR
- 失衡节点(图中 3)的 bf <-1,即右边更高
- 失衡节点的右孩子(图中 5 黄色)的 bf <= 0,即右孩子这边右边更高或等高
解决失衡
java
public class AVLTree<K extends Comparable<K>, V> {
private AVLNode<K, V> root;
//左旋,把要旋转的节点 -> 新的根节点
private AVLNode<K, V> leftRotate(AVLNode<K, V> node) {
/**
* 以node节点为 2:
* 2 4
* / \ / \
* 1 4 -> 2 5
* / \ / \ \
* 3 5 1 3 6
* \
* 6
*/
AVLNode<K, V> right = node.right; //4节点
AVLNode<K, V> rightLeft = right.left; //3节点
right.left = node; //设置4节点的左子树为2节点
node.right = rightLeft; //设置2节点的右子树为3节点
updateHeight(node); //更新树的高度
updateHeight(right); //更新树的高度
return right;
}
//右旋,把要旋转的节点 -> 新的根节点
private AVLNode<K, V> rightRotate(AVLNode<K, V> node) {
/**
* 以node节点为 8:
* 8 6
* / \ / \
* 6 9 -> 5 8
* / \ / \
* 5 7 7 9
*/
AVLNode<K, V> left = node.left; //6节点
AVLNode<K, V> leftRight = left.right; //7节点
left.right = node; //设置6节点的右子树为8节点
node.left = leftRight; //设置8节点的左子树为7节点
updateHeight(node); //更新树的高度
updateHeight(left); //更新树的高度
return left;
}
//先左旋再右旋
private AVLNode<K, V> leftRightRotate(AVLNode<K, V> node) {
/**
* 以node节点为 6:
* 6 6 4
* / \ / \ / \
* 2 7 4 7 2 6
* / \ 左旋后-> / \ 右旋后-> / \ / \
* 1 4 2 5 1 3 5 7
* / \ / \
* 3 5 1 3
*/
node.left = leftRotate(node.left);//节点2进行左旋,赋值给6的左子树
return rightRotate(node);//节点6右旋
}
//先右旋再左旋
private AVLNode<K, V> rightLeftRotate(AVLNode<K, V> node) {
/**
* 以node节点为 6:
* 2 2 4
* / \ / \ / \
* 1 6 1 4 2 6
* / \ 右旋后-> / \ 左旋后-> / \ / \
* 4 7 3 6 1 3 5 7
* / \ / \
* 3 5 5 7
*/
node.right = rightRotate(node.right);
return leftRotate(node);
}
//获取节点的高度
private int height(AVLNode<K, V> node) {
return node == null ? 0 : node.height;
}
//根据节点高度(新增,删除,旋转)
private void updateHeight(AVLNode<K, V> node) {
node.height = Integer.max(height(node.left), height(node.right)) + 1;
}
//平衡因子,左子树的高度 - 右子树的高度
private int balanceFactor(AVLNode<K, V> node) {
return height(node.left) - height(node.right);
}
//检查节点是否是平衡的二叉树,如果不是就旋转
private AVLNode<K, V> balance(AVLNode<K, V> node) {
if (node == null) {
return null;
}
int height = balanceFactor(node);//如果返回0,-1,1表示是平衡的,否则不是
if (height > 1 && balanceFactor(node.left) >= 0) {//右旋,要考虑=0的情况
return rightRotate(node);
} else if (height > 1 && balanceFactor(node.left) < 0) {//先左旋后右旋
return leftRightRotate(node);
} else if (height < -1 && balanceFactor(node.right) <= 0) {//左旋,要考虑=0的情况
return leftRotate(node);
} else if (height < -1 && balanceFactor(node.right) > 0) {//先右旋后左旋
return rightLeftRotate(node);
}
return node;
}
//添加
public void put(K key, V value) {
root = doPut(root, key, value);
}
private AVLNode<K, V> doPut(AVLNode<K, V> node, K key, V value) {
//1.找到空位, 创建新节点
if (node == null) {
return new AVLNode<>(key, value);
}
//2.key 已存在, 更新
if (key.compareTo(node.key) == 0) {
node.value = value;
return node;
}
//3.继续查找
if (key.compareTo(node.key) < 0) {
node.left = doPut(node.left, key, value);
} else {
node.right = doPut(node.right, key, value);
}
updateHeight(node);
return balance(node);
}
//删除
public void remove(K key) {
root = doRemove(root, key);
}
//递归执行,返回删除后的节点
private AVLNode<K, V> doRemove(AVLNode<K, V> node, K key) {
//1.node == null
if (node == null) {
return null;
}
//2.没有找到key
if (key.compareTo(node.key) > 0) {
node.right = doRemove(node.right, key);
} else if (key.compareTo(node.key) < 0) {
node.left = doRemove(node.left, key);
} else {
/**
* 找到key,删除操作分为四种情况
* 1.删除节点没有左子树,将右子树托孤给parent
* 2.删除节点没有右子树,将左子树托孤给parent
* 3.删除节点没有左右子树,将null托孤给parent
* 4.删除节点有左右子树,将后继节点托孤给parent
* 1.找删除节点的后继节点
* 2.处理后继节点
* 3.后继节点取代删除的节点
*/
if (node.left == null && node.right == null) {
return null;
} else if (node.left == null) {
node = node.right;
} else if (node.right == null) {
node = node.left;
} else {
AVLNode<K, V> p = node.right;
while (p.left != null) {
p = p.left;
}
p.right = doRemove(node.right, p.key);
p.left = node.left;
node = p;
}
}
//4.更新高度
updateHeight(node);
//5.平衡节点
return balance(node);
}
//查询
public V get(K key) {
AVLNode<K, V> pointer = root;
while (pointer != null) {
K k = pointer.key;
if (key.compareTo(k) > 0) {
pointer = pointer.right;
} else if (key.compareTo(k) < 0) {
pointer = pointer.left;
} else {
return pointer.value;
}
}
return null;
}
private static class AVLNode<K, V> {
private int height = 1;//节点的高度,默认是1
private K key;
private V value;
private AVLNode<K, V> left;
private AVLNode<K, V> right;
public AVLNode(K key, V value) {
this.key = key;
this.value = value;
}
public AVLNode(K key, V value, AVLNode<K, V> left, AVLNode<K, V> right) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
}
}
}