前言
普通的二叉搜索树(BST)有个致命缺陷:插入有序数据时会退化成链表。
cpp
BST<int> t;
for (int i = 1; i <= 1000; i++) t.insert(i); // 树退化成一条右斜链
// 查找复杂度从 O(log n) 变成 O(n)
AVL 树 是最早被发明的自平衡二叉搜索树(1962 年,Adelson-Velsky 和 Landis)。它通过旋转 维持左右子树高度差不超过 1,从而把最坏复杂度稳定在 O(log n)。
本文从平衡因子讲起,完整实现插入、旋转和删除。
一、核心概念
1.1 平衡因子(Balance Factor)
BF(node) = 左子树高度 - 右子树高度
AVL 树要求:每个节点的 BF ∈ {-1, 0, 1}。
只要有一个节点的 BF 变成 ±2,就说明失衡了,必须旋转修复。
1.2 为什么高度差限制在 1
这是 AVL 比红黑树更严格的地方:
| 树 | 平衡条件 | 树高上界 |
|---|
|-------|-------------|--------------|
| AVL 树 | 左右子树高度差 ≤ 1 | ≈ 1.44 log₂n |
|-----|-----------------|-----------|
| 红黑树 | 最长路径 ≤ 2 × 最短路径 | ≈ 2 log₂n |
代价与收益:
- AVL 查询更快 (树更矮),但插入删除更慢(旋转更频繁)。
- 红黑树插入删除更快,查询略慢。
所以:读多写少用 AVL,写多读少用红黑树 。这就是为什么 std::map 选了红黑树。
二、四种旋转
旋转是 AVL 的灵魂。只有四种情况,记住它们的形状即可。
2.1 LL 型(左左)→ 右旋
新节点插在左子树的左侧,导致失衡。
z(BF=2) y(BF=0)
/ \ / \
y(BF=1) T4 ==> x z
/ \ / \ / \
x T3 T1 T2 T3 T4
/ \
T1 T2
2.2 RR 型(右右)→ 左旋
新节点插在右子树的右侧。
z(BF=-2) y(BF=0)
/ \ / \
T1 y(BF=-1) ==> z x
/ \ / \ / \
T2 x T1 T2 T3 T4
/ \
T3 T4
2.3 LR 型(左右)→ 先左旋再右旋
新节点插在左子树的右侧。
z z x
/ \ / \ / \
y T4 ==> x T4 ==> y z
/ \ / \ / \ / \
T1 x y T3 T1 T2 T3 T4
/ \ / \
T2 T3 T1 T2
(先对 y 左旋) (再对 z 右旋)
2.4 RL 型(右左)→ 先右旋再左旋
新节点插在右子树的左侧,是 LR 的镜像。
判断口诀:
- 看失衡节点
z的 BF 是+2还是-2→ 决定往哪边旋 - 再看
z较重的那侧孩子的 BF → 决定是单旋还是双旋
三、完整实现
cpp
#include <iostream>
#include <algorithm>
#include <vector>
template <typename T>
class AVLTree {
private:
struct Node {
T key;
Node* left;
Node* right;
int height; // 以该节点为根的子树高度
explicit Node(const T& k)
: key(k), left(nullptr), right(nullptr), height(1) {}
};
Node* root_ = nullptr;
/* ---------- 基础工具 ---------- */
int height(Node* n) const { return n ? n->height : 0; }
int balanceFactor(Node* n) const {
return n ? height(n->left) - height(n->right) : 0;
}
void updateHeight(Node* n) {
if (n) n->height = 1 + std::max(height(n->left), height(n->right));
}
/* ---------- 旋转 ---------- */
// 右旋:处理 LL 型
Node* rotateRight(Node* z) {
Node* y = z->left;
Node* T3 = y->right;
y->right = z;
z->left = T3;
updateHeight(z); // 先更新下面的 z
updateHeight(y); // 再更新上面的 y
return y; // y 成为新的子树根
}
// 左旋:处理 RR 型
Node* rotateLeft(Node* z) {
Node* y = z->right;
Node* T2 = y->left;
y->left = z;
z->right = T2;
updateHeight(z);
updateHeight(y);
return y;
}
/* ---------- 插入 ---------- */
Node* insert(Node* node, const T& key) {
// 1. 标准 BST 插入
if (!node) return new Node(key);
if (key < node->key) node->left = insert(node->left, key);
else if (key > node->key) node->right = insert(node->right, key);
else return node; // 不允许重复键
// 2. 更新高度
updateHeight(node);
// 3. 计算平衡因子,判断是否需要旋转
int bf = balanceFactor(node);
// LL:左子树偏高,且新键插在左子树的左侧
if (bf > 1 && key < node->left->key)
return rotateRight(node);
// RR:右子树偏高,且新键插在右子树的右侧
if (bf < -1 && key > node->right->key)
return rotateLeft(node);
// LR:左子树偏高,但新键插在左子树的右侧
if (bf > 1 && key > node->left->key) {
node->left = rotateLeft(node->left);
return rotateRight(node);
}
// RL:右子树偏高,但新键插在右子树的左侧
if (bf < -1 && key < node->right->key) {
node->right = rotateRight(node->right);
return rotateLeft(node);
}
return node; // 未失衡
}
/* ---------- 删除 ---------- */
Node* minNode(Node* node) const {
while (node->left) node = node->left;
return node;
}
Node* remove(Node* node, const T& key) {
if (!node) return nullptr;
if (key < node->key) {
node->left = remove(node->left, key);
} else if (key > node->key) {
node->right = remove(node->right, key);
} else {
// 找到目标节点
if (!node->left || !node->right) {
// 至多一个孩子
Node* child = node->left ? node->left : node->right;
delete node;
return child;
}
// 两个孩子:用右子树最小值替换,再删除那个最小值
Node* succ = minNode(node->right);
node->key = succ->key;
node->right = remove(node->right, succ->key);
}
// 重新平衡(删除后失衡有四种,判断依据是孩子的 BF)
updateHeight(node);
int bf = balanceFactor(node);
if (bf > 1 && balanceFactor(node->left) >= 0)
return rotateRight(node);
if (bf > 1 && balanceFactor(node->left) < 0) {
node->left = rotateLeft(node->left);
return rotateRight(node);
}
if (bf < -1 && balanceFactor(node->right) <= 0)
return rotateLeft(node);
if (bf < -1 && balanceFactor(node->right) > 0) {
node->right = rotateRight(node->right);
return rotateLeft(node);
}
return node;
}
/* ---------- 遍历与清理 ---------- */
void inorder(Node* n, std::vector<T>& out) const {
if (!n) return;
inorder(n->left, out);
out.push_back(n->key);
inorder(n->right, out);
}
void destroy(Node* n) {
if (!n) return;
destroy(n->left);
destroy(n->right);
delete n;
}
// 校验:是否满足 BST 性质 + 平衡性质
bool check(Node* n, bool& balanced) const {
if (!n) return true;
if (n->left && n->left->key >= n->key) return false;
if (n->right && n->right->key <= n->key) return false;
int bf = balanceFactor(n);
if (bf < -1 || bf > 1) balanced = false;
return check(n->left, balanced) && check(n->right, balanced);
}
public:
AVLTree() = default;
~AVLTree() { destroy(root_); }
AVLTree(const AVLTree&) = delete; // 简化:禁止拷贝
AVLTree& operator=(const AVLTree&) = delete;
void insert(const T& key) { root_ = insert(root_, key); }
void remove(const T& key) { root_ = remove(root_, key); }
bool contains(const T& key) const {
Node* cur = root_;
while (cur) {
if (key < cur->key) cur = cur->left;
else if (key > cur->key) cur = cur->right;
else return true;
}
return false;
}
int height() const { return height(root_); }
std::vector<T> toVector() const {
std::vector<T> out;
inorder(root_, out);
return out;
}
bool isValid() const {
bool balanced = true;
return check(root_, balanced) && balanced;
}
};
四、测试
cpp
#include <cassert>
#include <random>
int main() {
AVLTree<int> t;
// 1. 插入有序数据(普通 BST 会退化成链,AVL 必须保持平衡)
for (int i = 1; i <= 1000; i++) t.insert(i);
assert(t.isValid());
std::cout << "插入 1000 个有序元素后,树高 = " << t.height() << '\n';
// 输出约 10~14,而普通 BST 会是 1000
// 2. 中序遍历必须有序
auto v = t.toVector();
for (size_t i = 1; i < v.size(); i++) assert(v[i - 1] < v[i]);
// 3. 随机打乱插入,对比高度
AVLTree<int> t2;
std::mt19937 gen(42);
std::vector<int> data(1000);
for (int i = 0; i < 1000; i++) data[i] = i;
std::shuffle(data.begin(), data.end(), gen);
for (int x : data) t2.insert(x);
std::cout << "随机插入 1000 个元素,树高 = " << t2.height() << '\n';
// 4. 删除后仍然平衡
for (int i = 1; i <= 500; i++) t.remove(i);
assert(t.isValid());
std::cout << "删除 500 个元素后,树高 = " << t.height() << '\n';
std::cout << "全部测试通过\n";
return 0;
}
预期输出(高度取决于具体实现,但都会远小于 1000):
插入 1000 个有序元素后,树高 = 10
随机插入 1000 个元素,树高 = 12
删除 500 个元素后,树高 = 10
全部测试通过
五、踩坑要点
坑 1:更新高度的顺序
cpp
// ❌ 错误:先更新 y 再更新 z,y 的高度用到了还没更新的 z
updateHeight(y);
updateHeight(z);
// ✅ 正确:先更新旋转后处于下层的节点
updateHeight(z);
updateHeight(y);
规则 :旋转后,先更新沉下去的那个节点。
坑 2:判断失衡类型不能只看平衡因子
插入 时可以直接比较 key 与孩子的大小;但删除 时不行 ------ 因为删除后 key 已经不在树里了,必须改用孩子的平衡因子判断:
cpp
// 删除时的判断依据
if (bf > 1 && balanceFactor(node->left) >= 0) // LL
if (bf > 1 && balanceFactor(node->left) < 0) // LR
if (bf < -1 && balanceFactor(node->right) <= 0) // RR
if (bf < -1 && balanceFactor(node->right) > 0) // RL
这是初学者最容易写错的地方。
坑 3:删除有两个孩子的节点
不要直接删!用右子树的最小值(中序后继)替换键值,再递归删除那个后继节点。这样保持了 BST 性质,且把问题转化成"删除至多一个孩子的节点"。
坑 4:高度定义不一致
本实现中空节点高度 = 0,叶子节点高度 = 1 。只要全程一致就没问题,但如果你混用"空节点高度 = -1"的定义,updateHeight 和 balanceFactor 都要跟着改,否则测试会莫名失败。
坑 5:AVL 的删除代价很高
删除一个节点,从它到根一路上所有失衡节点都要旋转 ,可能触发 O(log n) 次旋转。
相比之下,红黑树删除最多只需 3 次旋转。这是 AVL 写性能差的主要原因。
六、AVL vs 红黑树
| 维度 | AVL 树 | 红黑树 |
|---|
|--------|-------------|---------------|
| 平衡严格程度 | 高度差 ≤ 1(严格) | 最长 ≤ 2×最短(宽松) |
|----|--------------|-----------|
| 树高 | ≈ 1.44 log n | ≈ 2 log n |
|----|--------|----|
| 查找 | 更快 | 稍慢 |
|----|-------------------------|----------|
| 插入 | 最多 1 次旋转 + O(log n) 次更新 | 最多 2 次旋转 |
|----|---------------------|----------|
| 删除 | 最多 O(log n) 次旋转 | 最多 3 次旋转 |
|------|----|-----------|
| 实现难度 | 中等 | 较难(要处理颜色) |
|------|------------|--------------------------------------|
| 典型应用 | 内存数据库索引、字典 | std::map / std::set、Linux 内核 CFS |
选择建议:
- 场景以查询为主 ,很少改动(比如配置表、静态字典)→ AVL
- 场景增删频繁 ,查询也有一定量(比如通用容器)→ 红黑树
七、总结
- AVL 靠平衡因子(左右高度差 ∈ {-1,0,1})维持平衡,比红黑树更严格,树更矮。
- 旋转只有四种:LL 右旋、RR 左旋、LR 先左后右、RL 先右后左。
- 插入最多一次旋转即可恢复平衡;删除可能一路旋到根。
- 删除时判断失衡类型必须看孩子的平衡因子,这是最容易错的地方。
- 旋转后先更新下层节点的高度。
- AVL 适合读多写少,红黑树适合增删频繁。
理解 AVL 是理解红黑树的前置条件 ------ 红黑树本质上是用"颜色"来代替"严格高度差",用更少的旋转换取可接受的平衡度。
代码在 GCC 13 / C++17 下编译测试通过。如果对你有帮助,欢迎点赞收藏,有问题评论区交流。