AVL 树是最早提出的严格自平衡二叉搜索树(BST) ,由 Adelson-Velsky 和 Landis 在 1962 年提出。其核心特征是「任意节点的左右子树高度差的绝对值 ≤ 1」,通过严格的平衡约束,将树的高度严格控制在 O(logn)(n 为节点数),保证查找、插入、删除操作的时间复杂度稳定为 O(logn)。
与红黑树(近似平衡)相比,AVL 树的「严格平衡」带来了更高的查找效率,但插入 / 删除时的平衡调整成本更高(旋转次数更多),因此适用于「查询密集、修改稀疏」的场景。
一、AVL 树的核心定位
1. 解决的问题:普通 BST 的退化
普通 BST 若按有序序列插入(如 1→2→3→4→5),会退化为单链表,树高变为 n,操作复杂度从 O(logn) 退化为 O(n)。AVL 树通过「严格平衡规则 + 旋转调整」彻底解决这一问题。
2. AVL 树的核心优势与劣势
| 维度 | 优势 | 劣势 |
|---|---|---|
| 平衡程度 | 严格平衡(高度差≤1),树高更矮 | 平衡调整成本高(插入 / 删除旋转次数多) |
| 查找效率 | 更高(树高≈1.44×log₂(n+1),比红黑树矮) | 无 |
| 修改效率 | 更低(最多需 O (logn) 次旋转) | 无 |
| 适用场景 | 查询密集(如数据库索引、缓存) | 插入 / 删除频繁的场景(不如红黑树) |
二、AVL 树的基础定义
1. 核心概念
(1)节点高度(Height)
- 定义:从该节点到其最远叶子节点的路径长度(叶子节点高度为 0,空节点高度为 -1,简化平衡因子计算);
- 作用:用于计算「平衡因子」,判断节点是否失衡。
(2)平衡因子(Balance Factor, BF)
- 定义:
BF(node) = 左子树高度 - 右子树高度; - AVL 树的平衡规则:任意节点的
BF ∈ {-1, 0, 1},若 BF 超出此范围(即 |BF| > 1),则节点「失衡」,需通过旋转调整。
2. 节点结构(简化版)
AVL 树节点比普通 BST 多「高度」字段,用于实时计算平衡因子:
template <typename T>
struct AVLNode {
T key; // 节点值(键)
AVLNode* left; // 左孩子指针
AVLNode* right; // 右孩子指针
int height; // 节点高度(叶子节点初始为 0)
// 构造函数
AVLNode(T k) : key(k), left(nullptr), right(nullptr), height(0) {}
};
3. 辅助函数(高度 / 平衡因子计算)
// 获取节点高度(空节点返回 -1)
template <typename T>
int getHeight(AVLNode<T>* node) {
return node == nullptr ? -1 : node->height;
}
// 计算节点平衡因子
template <typename T>
int getBalanceFactor(AVLNode<T>* node) {
if (node == nullptr) return 0;
return getHeight(node->left) - getHeight(node->right);
}
// 更新节点高度(基于左右子树高度)
template <typename T>
void updateHeight(AVLNode<T>* node) {
node->height = max(getHeight(node->left), getHeight(node->right)) + 1;
}
三、AVL 树的核心操作:旋转(平衡调整的唯一手段)
AVL 树的旋转分为「单旋转」(左旋 / 右旋)和「双旋转」(左右旋 / 右左旋),所有旋转的核心目标是:调整节点位置,恢复失衡节点的平衡因子(|BF| ≤ 1),且不破坏 BST 特性(左小右大) 。
1. 单旋转:左旋(Left Rotate)
触发场景:右子树过高(RR 型失衡,后文详解)。
旋转逻辑:
以失衡节点 x 为轴,将其右孩子 y 「提上来」作为新父节点,x 变为 y 的左孩子,y 的原左孩子变为 x 的右孩子。
template <typename T>
AVLNode<T>* leftRotate(AVLNode<T>* x) {
// 步骤 1:保存关键节点
AVLNode<T>* y = x->right; // y 是 x 的右孩子
AVLNode<T>* T2 = y->left; // T2 是 y 的左子树
// 步骤 2:调整指针(核心旋转)
y->left = x;
x->right = T2;
// 步骤 3:更新 x 和 y 的高度(先更底下的 x,再更上层的 y)
updateHeight(x);
updateHeight(y);
// 返回新的根节点(y 取代 x 的位置)
return y;
}
2. 单旋转:右旋(Right Rotate)
触发场景:左子树过高(LL 型失衡,后文详解)。
旋转逻辑:
与左旋对称,以失衡节点 y 为轴,将其左孩子 x 「提上来」作为新父节点,y 变为 x 的右孩子,x 的原右孩子变为 y 的左孩子。
template <typename T>
AVLNode<T>* rightRotate(AVLNode<T>* y) {
// 步骤 1:保存关键节点
AVLNode<T>* x = y->left; // x 是 y 的左孩子
AVLNode<T>* T2 = x->right; // T2 是 x 的右子树
// 步骤 2:调整指针(核心旋转)
x->right = y;
y->left = T2;
// 步骤 3:更新 y 和 x 的高度
updateHeight(y);
updateHeight(x);
// 返回新的根节点(x 取代 y 的位置)
return x;
}
3. 双旋转:左右旋(LR Rotate)
触发场景:左子树的右子树过高(LR 型失衡)。
旋转逻辑:先对左孩子左旋,再对失衡节点右旋。
template <typename T>
AVLNode<T>* leftRightRotate(AVLNode<T>* z) {
// 步骤 1:对 z 的左孩子左旋
z->left = leftRotate(z->left);
// 步骤 2:对 z 右旋
return rightRotate(z);
}
4. 双旋转:右左旋(RL Rotate)
触发场景:右子树的左子树过高(RL 型失衡)。
旋转逻辑:先对右孩子右旋,再对失衡节点左旋。
template <typename T>
AVLNode<T>* rightLeftRotate(AVLNode<T>* z) {
// 步骤 1:对 z 的右孩子右旋
z->right = rightRotate(z->right);
// 步骤 2:对 z 左旋
return leftRotate(z);
}
四、AVL 树的核心操作:插入(Insert)
插入是 AVL 树的核心操作,逻辑为:先按 BST 插入 → 回溯更新高度 → 检查平衡因子 → 旋转调整失衡节点。
1. 插入的完整流程
template <typename T>
AVLNode<T>* insert(AVLNode<T>* root, T key) {
// 步骤 1:按普通 BST 规则插入新节点
if (root == nullptr) {
return new AVLNode<T>(key); // 空树,新建节点作为根
}
// 递归插入左/右子树
if (key < root->key) {
root->left = insert(root->left, key);
} else if (key > root->key) {
root->right = insert(root->right, key);
} else {
return root; // 重复值,不插入(AVL 树默认不允许重复)
}
// 步骤 2:更新当前节点的高度
updateHeight(root);
// 步骤 3:计算平衡因子,判断是否失衡
int bf = getBalanceFactor(root);
// 步骤 4:根据失衡类型,旋转调整
// 情况 1:LL 型失衡(左子树高,且新节点在左子树的左子树)
if (bf > 1 && getBalanceFactor(root->left) >= 0) {
return rightRotate(root);
}
// 情况 2:LR 型失衡(左子树高,且新节点在左子树的右子树)
if (bf > 1 && getBalanceFactor(root->left) < 0) {
return leftRightRotate(root);
}
// 情况 3:RR 型失衡(右子树高,且新节点在右子树的右子树)
if (bf < -1 && getBalanceFactor(root->right) <= 0) {
return leftRotate(root);
}
// 情况 4:RL 型失衡(右子树高,且新节点在右子树的左子树)
if (bf < -1 && getBalanceFactor(root->right) > 0) {
return rightLeftRotate(root);
}
// 未失衡,返回原节点
return root;
}
2. 四种失衡类型与旋转对应(核心)
| 失衡类型 | 平衡因子特征 | 旋转方式 | 示例场景(插入节点导致失衡) |
|---|---|---|---|
| LL | BF(root)=2,BF(root.left)=1 | 右旋 root | 插入节点到 root 左孩子的左子树 |
| LR | BF(root)=2,BF(root.left)=-1 | 左旋 root.left → 右旋 root | 插入节点到 root 左孩子的右子树 |
| RR | BF(root)=-2,BF(root.right)=-1 | 左旋 root | 插入节点到 root 右孩子的右子树 |
| RL | BF(root)=-2,BF(root.right)=1 | 右旋 root.right → 左旋 root | 插入节点到 root 右孩子的左子树 |
3. 插入示例(LL 型失衡调整)
初始 AVL 树(平衡):
3 (BF=1, height=1)
/
2 (BF=0, height=0)
插入节点 1 → 节点 3 失衡(BF=2):
3 (BF=2, height=2) // 失衡
/
2 (BF=1, height=1)
/
1 (BF=0, height=0)
右旋节点 3 后恢复平衡:
2 (BF=0, height=1)
/ \
1 3 (均 BF=0, height=0)
五、AVL 树的核心操作:删除(Delete)
删除比插入更复杂,逻辑为:先按 BST 删除节点 → 回溯更新高度 → 检查平衡因子 → 旋转调整失衡节点(可能递归调整到根) 。
1. 删除的完整流程
// 辅助:找到 BST 中最小节点(用于双孩子节点删除)
template <typename T>
AVLNode<T>* findMinNode(AVLNode<T>* node) {
AVLNode<T>* curr = node;
while (curr->left != nullptr) {
curr = curr->left;
}
return curr;
}
template <typename T>
AVLNode<T>* remove(AVLNode<T>* root, T key) {
// 步骤 1:按普通 BST 规则删除节点
if (root == nullptr) {
return nullptr; // 未找到待删除节点
}
// 递归查找待删除节点
if (key < root->key) {
root->left = remove(root->left, key);
} else if (key > root->key) {
root->right = remove(root->right, key);
} else {
// 找到待删除节点,分 3 种情况删除
// 情况 1:叶子节点/单孩子节点
if (root->left == nullptr || root->right == nullptr) {
AVLNode<T>* temp = root->left ? root->left : root->right;
// 叶子节点(无孩子)
if (temp == nullptr) {
temp = root;
root = nullptr;
} else { // 单孩子节点
*root = *temp; // 复制孩子节点数据
}
delete temp; // 释放内存
} else {
// 情况 2:双孩子节点 → 找中序后继(右子树最小节点)
AVLNode<T>* temp = findMinNode(root->right);
root->key = temp->key; // 替换值
root->right = remove(root->right, temp->key); // 删除后继节点
}
}
// 若树已空,返回 null
if (root == nullptr) {
return nullptr;
}
// 步骤 2:更新当前节点高度
updateHeight(root);
// 步骤 3:计算平衡因子,判断是否失衡
int bf = getBalanceFactor(root);
// 步骤 4:旋转调整(与插入的四种失衡类型完全一致)
// LL 型
if (bf > 1 && getBalanceFactor(root->left) >= 0) {
return rightRotate(root);
}
// LR 型
if (bf > 1 && getBalanceFactor(root->left) < 0) {
root->left = leftRotate(root->left);
return rightRotate(root);
}
// RR 型
if (bf < -1 && getBalanceFactor(root->right) <= 0) {
return leftRotate(root);
}
// RL 型
if (bf < -1 && getBalanceFactor(root->right) > 0) {
root->right = rightRotate(root->right);
return leftRotate(root);
}
// 未失衡,返回原节点
return root;
}
2. 删除的核心特点
- 删除后需要从「删除节点的父节点」开始回溯到根,逐个检查平衡因子(插入仅需回溯到第一个失衡节点);
- 即使当前节点失衡,旋转调整后可能导致其父节点失衡,因此需要递归调整;
- 旋转逻辑与插入完全一致(四种失衡类型对应相同旋转方式)。
六、AVL 树的查找操作(与普通 BST 一致)
AVL 树的查找逻辑无特殊之处,仅利用 BST「左小右大」的特性,时间复杂度 O(logn)(树高严格控制):
template <typename T>
AVLNode<T>* search(AVLNode<T>* root, T key) {
if (root == nullptr || root->key == key) {
return root; // 找到/未找到
}
if (key < root->key) {
return search(root->left, key); // 往左找
} else {
return search(root->right, key); // 往右找
}
}
七、AVL 树 vs 红黑树(核心对比)
| 特性 | AVL 树 | 红黑树 | |
|---|---|---|---|
| 平衡标准 | 严格平衡( BF ≤ 1) | 近似平衡(黑高一致,最长路径 ≤ 2× 最短路径) | |
| 树高 | ≈1.44×log₂(n+1)(更矮) | ≈2×log₂(n+1)(稍高) | |
| 插入旋转次数 | 最多 2 次(单次旋转即可修复,最坏递归 1 次) | 最多 2 次 | |
| 删除旋转次数 | 最多 O (logn) 次(需回溯到根) | 最多 2 次 | |
| 节点存储 | 需额外存储「高度」字段 | 需额外存储「颜色」字段 | |
| 工业应用 | 数据库索引、缓存(查询密集) | C++ STL set/map、Java TreeMap(修改密集) |
八、AVL 树的应用场景
- 数据库索引:MySQL 的 MEMORY 存储引擎中,索引可基于 AVL 树实现(查询效率优先);
- 缓存系统:如 Redis 的有序集合(zset)在元素较少时,曾用 AVL 树(现改为压缩列表 / 跳表);
- 编译器符号表:用于快速查找变量 / 函数名(查询远多于修改);
- 路由表:网络设备中用于快速查找路由条目(稳定性要求高)。
九、核心总结
- AVL 树是「严格平衡」的 BST,核心规则是「任意节点左右子树高度差 ≤ 1」;
- 平衡因子是判断失衡的核心,失衡后通过「单旋转 / 双旋转」恢复平衡;
- 插入:仅需回溯到第一个失衡节点,旋转 1-2 次即可修复;
- 删除:需回溯到根,最多需 O (logn) 次旋转,调整成本高;
- 对比红黑树:AVL 树查找更快,修改更慢,适用于「查询密集」场景。