AVL树¶
概述¶
AVL树是最早发明的**自平衡二叉搜索树**,由苏联数学家 Adelson-Velsky 和 Landis 在 1962 年提出。它通过在每个节点维护平衡因子,确保任意节点的左右子树高度差不超过 1,从而保证树的高度始终为 O(log n)。
核心性质
AVL树中任意节点的**平衡因子**(左子树高度 - 右子树高度)取值只能是 -1、0 或 1。当插入或删除操作导致平衡因子超出此范围时,通过**旋转操作**恢复平衡。
AVL树的历史意义¶
AVL树的历史意义
发明时间: 1962年
发明者: G.M. Adelson-Velsky 和 E.M. Landis
历史意义: - 第一个自平衡二叉搜索树 - 开创了平衡树研究的先河 - 为后续红黑树、B树等奠定基础
命名来源: AVL = Adelson-Velsky 和 Landis 的首字母缩写
AVL树特点¶
1. 严格平衡¶
AVL树要求任意节点的左右子树高度差不超过 1:
AVL树示例(高度标注)
graph TB
8((8<br/>h=3)) --> 4((4<br/>h=2))
8 --> 10((10<br/>h=1))
4 --> 2((2<br/>h=1))
4 --> 6((6<br/>h=1))
10 --> 12((12<br/>h=0))
2 --> 1((1<br/>h=0))
style 8 fill:#E3F2FD
style 4 fill:#E8F5E9
style 10 fill:#E8F5E9
验证平衡性: - 节点 8: |h(左)-h(右)| = |2-1| = 1 ≤ 1 ✓ - 节点 4: |h(左)-h(右)| = |1-1| = 0 ≤ 1 ✓ - 节点 2: |h(左)-h(右)| = |0-0| = 0 ≤ 1 ✓ - 节点 6: |h(左)-h(右)| = |0-0| = 0 ≤ 1 ✓ - 节点 10: |h(左)-h(右)| = |0-0| = 0 ≤ 1 ✓ - 节点 12: |h(左)-h(右)| = |0-0| = 0 ≤ 1 ✓ - 节点 1: |h(左)-h(右)| = |0-0| = 0 ≤ 1 ✓
这是一棵合法的 AVL 树
2. 平衡因子¶
每个节点维护一个平衡因子,用于判断是否需要调整:
平衡因子定义
BF(node) = height(node.left) - height(node.right)
平衡因子取值:
| 平衡因子 | 含义 | 状态 | 操作 |
|---|---|---|---|
| 1 | 左子树较高 | ✅ 平衡 | 无需调整 |
| 0 | 左右子树等高 | ✅ 平衡 | 无需调整 |
| -1 | 右子树较高 | ✅ 平衡 | 无需调整 |
| > 1 | 左子树过高 | ❌ 不平衡 | 需要右旋 |
| < -1 | 右子树过高 | ❌ 不平衡 | 需要左旋 |
3. 查找高效¶
AVL树保证查找、插入、删除的时间复杂度均为 O(log n):
高度上界分析
AVL树高度满足: h ≤ 1.44 × log₂(n + 2) - 0.328
证明思路:
设 n(h) 为高度为 h 的 AVL 树的最少节点数
递推关系: - n(0) = 1 - n(1) = 2 - n(h) = n(h-1) + n(h-2) + 1 (类似 Fibonacci)
因此: n ≥ Fibonacci(h+2) - 1
h ≤ 1.44 × log₂(n+2)
结论: AVL树高度为 O(log n)
4. 旋转调整¶
通过旋转操作恢复平衡,旋转是 O(1) 时间:
旋转操作类型
- 左旋(Left Rotation): 处理右子树过高
- 右旋(Right Rotation): 处理左子树过高
- 左右旋(LR): 先左旋后右旋
- 右左旋(RL): 先右旋后左旋
Tip
每次插入最多 1 次旋转
每次删除最多 O(log n) 次旋转
平衡因子¶
计算方法¶
平衡因子计算示例
graph TB
8((8)) --> 4((4))
8 --> 12((12))
4 --> 2((2))
4 --> 6((6))
12 --> 10((10))
2 --> 1((1))
style 8 fill:#E3F2FD
style 4 fill:#E3F2FD
style 12 fill:#E3F2FD
计算各节点平衡因子:
| 节点 | 左子树高度 | 右子树高度 | 平衡因子 | 状态 |
|---|---|---|---|---|
| 8 | 2 | 1 | 1 | ✅ 平衡 |
| 4 | 1 | 1 | 0 | ✅ 平衡 |
| 12 | 1 | 0 | 1 | ✅ 平衡 |
| 2 | 1 | 0 | 1 | ✅ 平衡 |
| 6 | 0 | 0 | 0 | ✅ 平衡 |
| 10 | 0 | 0 | 0 | ✅ 平衡 |
| 1 | 0 | 0 | 0 | ✅ 平衡 |
平衡因子变化¶
插入或删除节点后,平衡因子会发生变化:
插入节点导致的平衡因子变化
初始状态:
graph TB
5((5<br/>BF=0)) --> 3((3<br/>BF=0))
5 --> 7((7<br/>BF=0))
插入 1 后:
graph TB
5((5<br/>BF=1)) --> 3((3))
5 --> 7((7))
3 --> 1((1))
style 5 fill:#E8F5E9
插入 0 后(导致不平衡):
graph TB
5((5<br/>BF=2<br/>不平衡)) --> 3((3<br/>BF=1))
5 --> 7((7))
3 --> 1((1))
1 --> 0((0))
style 5 fill:#EF9A9A
style 3 fill:#FFCC80
原理详解¶
四种旋转情况¶
当节点失衡时,根据失衡模式选择对应的旋转方式:
四种旋转情况
| 类型 | 条件 | 问题 | 解决 |
|---|---|---|---|
| LL型(左左) | 失衡节点 BF > 1 且左子节点 BF ≥ 0 | 在左子树的左子树插入导致失衡 | 一次右旋 |
| RR型(右右) | 失衡节点 BF < -1 且右子节点 BF ≤ 0 | 在右子树的右子树插入导致失衡 | 一次左旋 |
| LR型(左右) | 失衡节点 BF > 1 且左子节点 BF < 0 | 在左子树的右子树插入导致失衡 | 先左旋后右旋 |
| RL型(右左) | 失衡节点 BF < -1 且右子节点 BF > 0 | 在右子树的左子树插入导致失衡 | 先右旋后左旋 |
右旋操作(LL型)¶
当左子树的左子树插入节点导致失衡时,执行右旋:
右旋操作详解
旋转前(LL型失衡):
graph TB
z((z<br/>BF=2)) --> y((y))
z --> T3((T3))
y --> x((x))
y --> T2((T2))
x --> T1((T1))
style z fill:#EF9A9A
style y fill:#FFCC80
分析: - z 的左子树高度为 2,右子树高度为 0 - BF(z) = 2 > 1,需要调整 - 这是 LL 型,执行右旋
右旋过程: 1. 保存 y 和 T2 2. 将 z 变为 y 的右子节点 3. 将 T2 变为 z 的左子节点
旋转后:
graph TB
y((y<br/>BF=0或1)) --> x((x))
y --> z((z))
x --> T1((T1))
z --> T2((T2))
z --> T3((T3))
style y fill:#C8E6C9
验证: - y 成为新的根 - z 降为 y 的右子节点 - T2 从 y 的右子树变为 z 的左子树 - 中序遍历顺序不变: T1, x, T2, z, T3
左旋操作(RR型)¶
当右子树的右子树插入节点导致失衡时,执行左旋:
左旋操作详解
旋转前(RR型失衡):
graph TB
x((x<br/>BF=-2)) --> T1((T1))
x --> y((y))
y --> T2((T2))
y --> z((z))
z --> T3((T3))
z --> T4((T4))
style x fill:#EF9A9A
style y fill:#FFCC80
style z fill:#E3F2FD
分析: - x 的左子树高度为 0,右子树高度为 2 - BF(x) = -2 < -1,需要调整 - 这是 RR 型,执行左旋
左旋过程: 1. 保存 y 和 T2 2. 将 x 变为 y 的左子节点 3. 将 T2 变为 x 的右子节点
旋转后:
graph TB
y((y<br/>BF=0或-1)) --> x((x))
y --> z((z))
x --> T1((T1))
x --> T2((T2))
z --> T3((T3))
z --> T4((T4))
style y fill:#C8E6C9
style x fill:#E3F2FD
style z fill:#E3F2FD
验证: - y 成为新的根 - x 降为 y 的左子节点 - T2 从 y 的左子树变为 x 的右子树 - 中序遍历顺序不变: T1, x, T2, y, T3, z, T4
双旋转(LR型和RL型)¶
当需要在两个不同方向的子树上旋转时,使用双旋转:
LR型双旋转(先左旋后右旋)
初始状态(LR型失衡):
graph TB
z((z<br/>BF=2)) --> y((y))
z --> T4((T4))
y --> T1((T1))
y --> x((x))
x --> T2((T2))
x --> T3((T3))
style z fill:#EF9A9A
style y fill:#FFCC80
style x fill:#E3F2FD
分析: - BF(z) = 2 > 1,左子树过高 - BF(y) = -1 < 0,左子树的右子树更高 - 这是 LR 型
步骤1: 对 y 左旋
graph TB
z((z)) --> x((x))
z --> T4((T4))
x --> y((y))
x --> T3((T3))
y --> T1((T1))
y --> T2((T2))
style x fill:#E3F2FD
style y fill:#FFCC80
步骤2: 对 z 右旋
graph TB
x((x)) --> y((y))
x --> z((z))
y --> T1((T1))
y --> T2((T2))
z --> T3((T3))
z --> T4((T4))
style x fill:#C8E6C9
style y fill:#E3F2FD
style z fill:#E3F2FD
验证: - 最终平衡 - 中序遍历顺序不变: T1, y, T2, x, T3, z, T4
RL型双旋转(先右旋后左旋)
初始状态(RL型失衡):
graph TB
x((x<br/>BF=-2)) --> T1((T1))
x --> z((z))
z --> y((y))
z --> T4((T4))
y --> T2((T2))
y --> T3((T3))
style x fill:#EF9A9A
style z fill:#FFCC80
style y fill:#E3F2FD
分析: - BF(x) = -2 < -1,右子树过高 - BF(z) = 1 > 0,右子树的左子树更高 - 这是 RL 型
步骤1: 对 z 右旋
graph TB
x((x)) --> T1((T1))
x --> y((y))
y --> z((z))
y --> T2((T2))
z --> T3((T3))
z --> T4((T4))
style x fill:#E3F2FD
style y fill:#E3F2FD
style z fill:#FFCC80
步骤2: 对 x 左旋
graph TB
y((y)) --> x((x))
y --> z((z))
x --> T1((T1))
x --> T2((T2))
z --> T3((T3))
z --> T4((T4))
style y fill:#C8E6C9
style x fill:#E3F2FD
style z fill:#E3F2FD
验证: - 最终平衡 - 中序遍历顺序不变: T1, x, T2, y, T3, z, T4
可视化演示¶
插入操作完整演示¶
插入序列: 10, 20, 30, 40, 50, 25
步骤1: 插入 10¶
graph TB
10((10))
style 10 fill:#E3F2FD
步骤2: 插入 20¶
graph TB
10((10)) --> 20((20))
style 10 fill:#E3F2FD
style 20 fill:#E3F2FD
检查: BF(10) = -1,平衡 ✓
步骤3: 插入 30(RR型失衡)¶
插入后(不平衡):
graph TB
10((10<br/>BF=-2)) --> 20((20<br/>BF=-1))
20 --> 30((30))
style 10 fill:#EF9A9A
style 20 fill:#FFCC80
失衡类型: RR型 → 对 10 执行左旋
左旋后(恢复平衡):
graph TB
20((20)) --> 10((10))
20 --> 30((30))
style 20 fill:#C8E6C9
style 10 fill:#E3F2FD
style 30 fill:#E3F2FD
检查: BF(20)=0, BF(10)=0, BF(30)=0,全部平衡 ✓
步骤4: 插入 40¶
graph TB
20((20)) --> 10((10))
20 --> 30((30))
30 --> 40((40))
style 20 fill:#E3F2FD
检查: BF(30) = -1, BF(20) = -1,平衡 ✓
步骤5: 插入 50(RR型失衡)¶
插入后(不平衡):
graph TB
20((20<br/>BF=-2)) --> 10((10))
20 --> 30((30<br/>BF=-1))
30 --> 40((40<br/>BF=-1))
40 --> 50((50))
style 20 fill:#EF9A9A
style 30 fill:#FFCC80
style 40 fill:#E3F2FD
失衡类型: RR型 → 对 30 执行左旋
左旋后(恢复平衡):
graph TB
20((20)) --> 10((10))
20 --> 40((40))
40 --> 30((30))
40 --> 50((50))
style 20 fill:#C8E6C9
检查: 全部平衡 ✓
步骤6: 插入 25(RL型失衡)¶
插入后(不平衡):
graph TB
20((20<br/>BF=-2)) --> 10((10))
20 --> 40((40<br/>BF=1))
40 --> 30((30<br/>BF=1))
40 --> 50((50))
30 --> 25((25))
style 20 fill:#EF9A9A
style 40 fill:#FFCC80
分析: - BF(20) = -2 < -1,右子树过高 - BF(40) = 1 > 0,右子树的左子树更高 - 失衡类型: RL型
步骤1: 对 40 右旋
graph TB
20((20)) --> 10((10))
20 --> 30((30))
30 --> 25((25))
30 --> 40((40))
40 --> 50((50))
步骤2: 对 20 左旋
graph TB
30((30)) --> 20((20))
30 --> 40((40))
20 --> 10((10))
20 --> 25((25))
40 --> 50((50))
style 30 fill:#C8E6C9
检查: 全部平衡 ✓
最终 AVL 树¶
graph TB
30((30)) --> 20((20))
30 --> 40((40))
20 --> 10((10))
20 --> 25((25))
40 --> 50((50))
style 30 fill:#E3F2FD
style 20 fill:#E8F5E9
style 40 fill:#E8F5E9
中序遍历: 10 → 20 → 25 → 30 → 40 → 50(有序)
旋转操作流程图¶
flowchart TB
A[插入/删除节点] --> B[更新祖先节点高度]
B --> C[检查平衡因子]
C --> D{BF 是否在 -1,0,1 ?}
D -->|是| E[操作完成]
D -->|否| F{BF > 1 ?}
F -->|是| G{左子节点 BF ≥ 0 ?}
G -->|是| H[LL型: 右旋]
G -->|否| I[LR型: 先左旋后右旋]
F -->|否| J{右子节点 BF ≤ 0 ?}
J -->|是| K[RR型: 左旋]
J -->|否| L[RL型: 先右旋后左旋]
H --> M[更新高度]
I --> M
K --> M
L --> M
M --> E
style E fill:#ccffcc
代码实现¶
节点定义¶
右旋操作¶
| C | |
|---|---|
左旋操作¶
| C | |
|---|---|
平衡调整¶
插入操作¶
删除操作¶
查找操作¶
| C | |
|---|---|
C++ 模板实现¶
复杂度分析¶
时间复杂度¶
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 查找 | O(log n) | 树高度为 O(log n) |
| 插入 | O(log n) | 查找 O(log n) + 旋转 O(1) |
| 删除 | O(log n) | 查找 O(log n) + 旋转 O(log n) |
| 旋转 | O(1) | 仅修改指针 |
| Text Only | |
|---|---|
空间复杂度¶
- O(n):存储 n 个节点
- 每个节点额外存储高度值:O(1)
AVL树性质¶
高度上界¶
最少节点数¶
高度为 h 的 AVL 树的最少节点数满足递推关系:n(h) = n(h-1) + n(h-2) + 1
| 高度 h | 最少节点数 n(h) | 计算过程 |
|---|---|---|
| 0 | 1 | 基础情况 |
| 1 | 2 | 基础情况 |
| 2 | 4 | n(1) + n(0) + 1 = 2 + 1 + 1 |
| 3 | 7 | n(2) + n(1) + 1 = 4 + 2 + 1 |
| 4 | 12 | n(3) + n(2) + 1 = 7 + 4 + 1 |
| 5 | 20 | n(4) + n(3) + 1 = 12 + 7 + 1 |
规律: n(h) ≈ Fibonacci(h+2) - 1
graph TB
A[n(h)] --> B[n(h-1)]
A --> C[n(h-2)]
A --> D[+ 1]
style A fill:#E3F2FD
AVL树验证¶
AVL vs 红黑树¶
AVL树与红黑树对比
| 特性 | AVL树 | 红黑树 |
|---|---|---|
| 平衡程度 | 严格平衡(高度差 ≤ 1) | 近似平衡(黑高差 ≤ 1) |
| 高度上界 | h ≤ 1.44 × log₂(n) | h ≤ 2 × log₂(n+1) |
| 查找效率 | 更优(树更矮) | 稍差(树略高) |
| 插入旋转 | 最多 1 次 | 最多 2 次 |
| 删除旋转 | 最多 O(log n) 次 | 最多 3 次 |
| 空间开销 | 存储高度值 | 存储颜色位 |
| 适用场景 | 查找密集型场景 | 插入删除频繁场景 |
flowchart LR
subgraph AVL树["AVL树"]
A1[严格平衡]
A2[高度: 1.44×log(n)]
A3[查找更快]
A4[旋转更多]
end
subgraph 红黑树["红黑树"]
B1[近似平衡]
B2[高度: 2×log(n)]
B3[查找稍慢]
B4[旋转较少]
end
A1 --- B1
A2 --- B2
A3 --- B3
A4 --- B4
style AVL树 fill:#E3F2FD,stroke:#2196F3
style 红黑树 fill:#FFF3E0,stroke:#FF9800
应用场景对比¶
| 场景类型 | AVL树适用 | 红黑树适用 |
|---|---|---|
| 数据库索引 | 内存索引 | - |
| 编程语言容器 | - | C++ STL map/set, Java TreeMap |
| 内存数据库 | Redis Sorted Set(跳表) | - |
| 编译器符号表 | ✓ | - |
| Linux进程调度 | - | ✓ |
应用场景¶
1. 数据库索引¶
| Text Only | |
|---|---|
2. 内存数据库¶
| Text Only | |
|---|---|
3. 编译器符号表¶
| Text Only | |
|---|---|
4. 事件调度¶
| Text Only | |
|---|---|
5. 文件系统¶
| Text Only | |
|---|---|
参考资料¶
- 《算法导论》第12章 - 二叉搜索树
- Adelson-Velsky, G. and Landis, E. M. (1962). "An algorithm for the organization of information"
- LeetCode 110. 平衡二叉树
- AVL Tree Visualization - cs.usfca.edu