树的家族全景:从二叉树到 B+ 树
一条主线读完:二叉树 → BST → AVL / 红黑树 → B 树 → B+ 树 。
核心只有一句话:每一层都是在上一个结构上"加一条约束"或"换一种平衡手段",约束越多能力越专,平衡手段越松实现越稳。
目录
- 总览:关系地图
- 第一层:普通二叉树
- [第二层:二叉搜索树 BST](#第二层:二叉搜索树 BST)
- [第三层:AVL 树](#第三层:AVL 树)
- 第三层(并列):红黑树
- [第四层:B 树](#第四层:B 树)
- [第五层:B+ 树](#第五层:B+ 树)
- 关系总结表
- 各类"平衡手段"对照
- 一张图看全家族
一、总览:关系地图
1.1 继承关系(谁是谁的特例)

┌─────────────────────┐
│ 树(一般树) │
└──────────┬──────────┘
│ 加约束:每点最多 2 个孩子 + 左右有序
▼
┌─────────────────────┐
│ 二叉树 │ ← 只规定"形状"
└──────────┬──────────┘
│ 加约束:中序单调(左 < 根 < 右)
▼
┌─────────────────────┐
│ 二叉搜索树 BST │ ← 规定"值的顺序"
└──────────┬──────────┘
│ 加约束:平衡(防止退化成链表)
┌──────────────┴──────────────┐
▼ ▼
┌───────────────┐ ┌───────────────┐
│ AVL 树 │ │ 红黑树 │
│ 高度差 ≤ 1 │ │ 颜色规则约束 │
│ 严格平衡 │ │ 近似平衡 │
└───────────────┘ └───────────────┘
--------------------------- 以上都是"二叉"(每点 ≤ 2 孩子)------------------------
┌─────────────────────┐
│ 多路平衡树 │ ← 换维度:每点可有 m 个孩子
└──────────┬──────────┘
▼
┌─────────────────────┐
│ B 树 │
│ 所有结点可存数据 │
└──────────┬──────────┘
│ 变形:数据全下沉到叶子 + 叶子串链表
▼
┌─────────────────────┐
│ B+ 树 │
│ 仅叶子存数据 │
└─────────────────────┘
1.2 一句话定位各个结构
| 结构 | 一句话定位 |
|---|---|
| 二叉树 | 只规定形状:每点最多两个孩子,左右有序 |
| BST | 二叉树 + 值的排序约束,中序有序 |
| AVL | BST + 严格高度约束,左右子树高度差 ≤ 1 |
| 红黑树 | BST + 颜色约束,只保证"最长路径 ≤ 2×最短路径" |
| B 树 | 多路平衡树,结点可存数据,叶子全在同一层 |
| B+ 树 | B 树变体,数据全下沉到叶子,叶子串成有序链表 |
二、第一层:普通二叉树
2.1 特点
- 约束最少:只有两条------每点最多两个孩子、左右孩子有区分
- 没有值约束:结点可以不存值(纯形状结构)
- 没有平衡约束:可以长成任何歪样子,甚至退化成一条链
- 是"母结构":后面所有二叉结构都是它的特例
2.2 规则
1. 每个结点最多 2 个子结点
2. 左右子树有次序,不能互换(左就是左,右就是右)
3. 没有其他约束
2.3 图示

合法的二叉树 也合法(歪的) 也合法
1 1 1
/ \ \ \
2 3 2 2
/ \ \ \
4 5 3 3
/ \
4 5
2.4 计数性质
叶子数 n0 = 度为 2 的结点数 n2 + 1
总结点 n = n0 + n1 + n2
边数 = n − 1 = n1 + 2·n2
n 个结点的形状数 = 卡特兰数 C(2n, n) / (n+1)
2.5 遍历
| 遍历 | 顺序 |
|---|---|
| 前序 | 根 → 左 → 右 |
| 中序 | 左 → 根 → 右 |
| 后序 | 左 → 右 → 根 |
| 层序 | 逐层从左到右 |
2.6 构造规则(从序列还原)
| 组合 | 唯一性 |
|---|---|
| 前序 + 中序 | 唯一 |
| 后序 + 中序 | 唯一 |
| 层序 + 中序 | 唯一 |
| 前序 + 后序 | 不唯一 |
| 单独给任何一种 | 无法还原 |
核心手法:定根 → 在中序里定位 → 按长度切左右区间 → 递归。
三、第二层:二叉搜索树 BST
3.1 特点
- 在二叉树上加了"值的顺序约束"
- 中序遍历 = 升序序列(这是 BST 的灵魂性质)
- 查找、插入、删除平均 O(log n),但取决于树高
3.2 规则
1. 继承二叉树的全部规则
2. 左子树所有值 < 根值
3. 右子树所有值 > 根值
4. (推论)中序遍历得到升序
3.3 图示

8
/ \
3 10
/ \ \
1 6 14
/ \ /
4 7 13
中序遍历:1 3 4 6 7 8 10 13 14 (升序 ✓)
3.4 致命缺陷:退化成链表

插入有序序列 1,2,3,4,5:
1
\
2
\
3
\
4
\
5 ← 树高 = n,退化成链表,O(log n) 变 O(n)
3.5 三大操作

| 操作 | 做法 |
|---|---|
| 查找 | 小于走左、大于走右 |
| 插入 | 走到空位挂上(新结点一定是叶子) |
| 删除 | 分三种:度 0 直接删;度 1 用孩子顶上;度 2 用中序前驱/后继的值替换,再转删替身 |
删除度为 2 的结点(重点):
原树:
8
/ \
3 10
/ \
1 6
/ \
4 7
删 3(度为 2)
┌─ 步骤 1:找中序后继(右子树最小)= 4
├─ 步骤 2:用 4 的值覆盖 3
└─ 步骤 3:转而删除原来的 4(它是叶子,直接删)
结果:
8
/ \
4 10
/ \
1 6
\
7
记忆 :度为 2 的结点不是被删掉,而是被"替身"顶替,让替身去死------替身天然缺一条腿,问题降维成"接一根线"。
四、第三层:AVL 树
4.1 特点
- BST + 严格平衡
- 每个结点的左右子树高度差 ≤ 1(平衡因子 BF ∈ {−1, 0, +1})
- 树最矮、查找最快
- 插入删除代价高(可能多次旋转)
4.2 规则
1. 继承 BST 的全部规则
2. 每个结点 BF = 左子树高 − 右子树高 ∈ {−1, 0, +1}
3. 一旦 BF 达到 ±2,立刻旋转修正
4.3 平衡因子与四种失衡
| 类型 | bf(失衡结点) | 高侧孩子的 bf | 方向关系 | 形状 |
|---|---|---|---|---|
| LL | +2 | +1 或 0 | 同号 | 直线,一路向左 |
| LR | +2 | −1 | 异号 | 折线,先左后右 |
| RR | −2 | −1 或 0 | 同号 | 直线,一路向右 |
| RL | −2 | +1 | 异号 | 折线,先右后左 |
4.4 四种失衡图示

LL(直线) RR(直线) LR(折线) RL(折线)
z(+2) z(-2) z(+2) z(-2)
/ \ / \
y(+1) y(-1) y(-1) y(+1)
/ \ \ /
x x x x
一路向左 一路向右 先左后右 先右后左
4.5 旋转判据(核心)
看 z 到新长高结点的"两级方向"是否一致:
- 同向(直线,LL/RR)→ 单旋
- 异向(折线,LR/RL)→ 双旋(先掰直,再单旋)
| 类型 | 第一步 | 第二步 |
|---|---|---|
| LL | 对 z 右旋 | --- |
| RR | 对 z 左旋 | --- |
| LR | 对 z 的左孩子 左旋(掰直) | 对 z 右旋 |
| RL | 对 z 的右孩子 右旋(掰直) | 对 z 左旋 |
记忆口诀:
字母相同 = 单旋,方向与字母相反;字母不同 = 双旋,先旋孩子(掰直),再旋自己。
4.6 旋转的机械动作

左旋(以 p 为轴,右孩子 q 顶上来): 右旋(以 p 为轴,左孩子 q 顶上来):
p q p q
/ \ / \ / \ / \
A q ──→ p C q C ──→ A p
/ \ / \ / \ / \
B C A B A B B C
口诀:左旋 → "冲突的左孩变右孩"(B 挂到 p 右边)
右旋 → "冲突的右孩变左孩"(B 挂到 p 左边)
顺序永远是:先摘掉冲突孩子 → 再挂新父 → 最后接回原结点
4.7 LR 双旋完整过程

初始(LR 型):
z
/
y
\
x
步骤 1:对 y 左旋(掰直)
z
/
x
/
y
步骤 2:对 z 右旋
x
/ \
y z
五、第三层(并列):红黑树
5.1 特点
- BST + 颜色约束(不存高度)
- 近似平衡:只保证"最长路径 ≤ 2 × 最短路径"
- 树比 AVL 高一点,查找略慢
- 写操作非常便宜:插入最多 2 次旋转,删除最多 3 次旋转
- 工业界宠儿 :
std::map、TreeMap、Linux 内核rbtree
5.2 五条游戏规则
1. 每个结点非红即黑
2. 根结点是黑的
3. 所有叶子(NIL 空结点)是黑的
4. 红结点的孩子必须是黑(不能红红相连)
5. 从任一结点到它所有后代 NIL 的路径上,黑结点数相同(黑高相等)
5.3 规则 4 + 规则 5 的推论
最长路径 ≤ 2 × 最短路径 → 树高 h ≤ 2·log₂(n+1) → 所有操作 O(log n)
证明思路:最短路径全是黑的(长度 = 黑高 bh);最长路径黑红交替,每两个结点只有 1 个黑(长度 = 2bh)。
5.4 图示

[10 黑]
/ \
[5 红] [15 黑]
/ \ / \
[3 黑] [7 黑] [12 红] [18 黑]
\
[20 红] ← 红的孩子必是黑
根到各叶的黑结点数都相等 = 黑高一致 ✓
5.5 插入修复(三种情况)

前提:新结点染红(只可能破坏规则 4,不破坏黑高)。设 z、父 p、祖父 g、叔 u。
| 情况 | 条件 | 操作 |
|---|---|---|
| Case 1:叔红 | u 是红 | p、u 变黑,g 变红,z 上移到 g |
| Case 2:叔黑 + 折线 | z 与 p 方向不一致 | 对 p 旋转(掰直),进 Case 3 |
| Case 3:叔黑 + 直线 | z 与 p 方向一致 | p 变黑、g 变红,对 g 旋转,结束 |
关键 :红黑树没有 LR/RL 分类 ,它只判断"z 与 p 是否同向(直线 vs 折线)"。
Case 1(叔红):只变色,不旋转,冲突向上传播
g(黑) g(红)
/ \ / \
p(红) u(红) ──→ p(黑) u(黑)
/ /
z(红) z(红) → 把 g 当新的 z 继续上溯
Case 2 → Case 3(叔黑):
折线: 掰直后: 直线收口:
g g p(黑)
/ / / \
p z(红) z(红) g(红)
\ /
z(红) p
先对 p 旋转 变成直线 对 g 旋转
5.6 删除修复(双黑问题)
删掉黑结点 → 那条路径黑高少 1 → 产生"双黑"结点 x。看兄弟 w:
| 情况 | 条件 | 操作 |
|---|---|---|
| Case 1 | w 红 | w 变黑、父变红,对父旋转,转入后面情况 |
| Case 2 | w 黑,两孩子都黑 | w 变红,双黑上移给父 |
| Case 3 | w 黑,近侧孩子红、远侧黑 | 变色 + 对 w 旋转,转入 Case 4 |
| Case 4 | w 黑,远侧孩子红 | 大调色 + 对父旋转,结束 |
5.7 与 AVL 的对照
| 维度 | AVL | 红黑树 |
|---|---|---|
| 平衡判据 | 显式高度,BF ∈ {−1,0,1} | 颜色规则,隐式 |
| 严格度 | 严格(树更矮) | 宽松(树可能高近 2 倍) |
| 查找 | 略快 | 略慢 |
| 插入旋转 | 最多 2 次 | 最多 2 次,多为变色 |
| 删除旋转 | 可多次上溯,O(log n) | 最多 3 次 |
| 适用场景 | 查询密集 | 写密集 |
5.8 本质:红黑树 ≡ 2-3-4 树

把红结点"合并"进父结点:
[10 黑]
/ \
[5 红] ...
/ \
↓ 合并红孩子
[ 5 | 10 ] ← 一个结点装 2 个键 = 2-3-4 树的 3-结点
/ | \
3 7 ...
红黑树的颜色 = 把 2-3-4 树的"胖结点"拍扁成二叉树后的标记。 这就是它旋转少的原因------本质上它是在一棵 B 树里做局部调整。
六、第四层:B 树
6.1 特点
- 多路(m 叉)平衡树,不再限于 2 个孩子
- 所有结点都可以存数据
- 所有叶子在同一层(这是 B 树的命根子)
- 为磁盘存储而生:一次 I/O 读一个结点,结点越胖树越矮
6.2 规则(m 阶 B 树)
1. 每个结点最多 m 个孩子(m−1 个关键字)
2. 根结点至少 2 个孩子(除非它本身就是叶子)
3. 除根外,每个非叶结点至少 ⌈m/2⌉ 个孩子(即至少 ⌈m/2⌉−1 个关键字)
4. 所有叶子都在同一层 ← 完美平衡
5. 结点内关键字有序:k1 < k2 < ...
6. 有 n 个关键字的结点有 n+1 个孩子
6.3 结点结构
[ P0 | k1 | P1 | k2 | P2 | ... | kn | Pn ]
│ │ │ │
↓ ↓ ↓ ↓
< k1 k1~k2之间 k2~k3之间 > kn
读 B 树结点图,只需要分清两样东西:
- 值是关键字(key) :结点里存着的那些数字(10、20、30...)叫关键字,它是"用来比较、决定下一步往哪走"的键。
- 指针是孩子(child) :B 树里结点之间是靠指针 连起来的------父结点通过这些孩子指针,指向它下面的若干个(根以外每个结点是 m 个)子结点。指针本身就等于孩子,指针有几个,孩子就有几个。
由此得到两条务必记住的推论:
| 看什么 | 立刻能得出什么 |
|---|---|
| 一个结点里有几个指针 | 它就有几个孩子 |
| 一个结点里有几个关键字 | 孩子数 = 关键字数 + 1 |
指针 = 孩子,值 = 关键字。
指针夹在关键字之间排开,天然切出"小于 k1 / 落在 k1 与 k2 之间 / 大于 kn"这几个区间------这正是父结点能一次就把"该往哪个孩子走"定下来的原因。
放到二叉树上其实是同一件事,只是"孩子位"只有两个:左右孩子指针就是它的两个孩子,B 树无非是把这个孩子位从 2 个扩到了 m 个。
6.4 图示(4 阶 B 树)

[ 20 | 40 ]
/ | \
[10] [25|30] [45|50]
所有叶子在同一层 ✓
结点内关键字有序 ✓
4 阶 → 最多 3 个关键字、最多 4 个孩子 ✓
6.5 插入:分裂

规则 :结点满了(关键字数 = m−1)就从中间劈开,中位键上提到父结点。
插入 10, 20, 30:
[10 | 20 | 30]
插入 40 → 满了(4 个),中位键 20 上提:
[20]
/ \
[10] [30 | 40]
插入 50, 60 → 右结点满,40 上提:
[20 | 40]
/ | \
[10] [30] [50 | 60]
插入 70 → 根满,继续劈,40 上提成新根 → 树长高:
[40]
/ \
[20] [60]
/ \ / \
[10] [30] [50] [70]
关键结论:
- B 树长高的唯一方式 = 根分裂
- B 树是"从下往上长"的(先分裂叶子,上传,根分裂才长高)
- 全程没有旋转,只有分裂
6.6 删除:借 / 合并
1. 叶子结点关键字够 → 直接删
2. 不够(< ⌈m/2⌉−1)→ 向兄弟"借":通过父结点中转一个关键字过来
3. 兄弟也不够 → 与兄弟"合并",父结点少一个关键字
4. 父结点也不够了 → 继续向上借/合并
5. 合并到根 → 树矮一层
6.7 B 树不需要判断 LR/RL
B 树的平衡不靠局部旋转,而靠"分裂/合并"。
它的平衡条件是全局 的------"所有叶子同层",由分裂上提、合并下沉自动维持。
判断依据只有两个:关键字数是否越界、叶子是否同层。
七、第五层:B+ 树
7.1 与 B 树的四大差异
| 维度 | B 树 | B+ 树 |
|---|---|---|
| 数据存放 | 所有结点都可能存数据 | 只有叶子存数据 |
| 内部结点 | 键 + 数据 + 指针 | 仅键 + 指针(纯索引) |
| 关键字重复 | 每个键出现一次 | 内部键在叶子重复出现(叶子=全量) |
| 叶子链表 | 无 | 所有叶子串成有序双向链表 |
7.2 图示

[ 20 | 40 ] ← 内部结点:只当路标,不存数据
/ | \
[10|15] [25|30] [45|50] ← 叶子层:存全部键 + 数据
↕ ↕ ↕
←──── 有序双向链表(叶子之间串起来)────→
查找 30:根 → 中子树 → 叶子命中(必到叶子,路径等长)
范围查询 15~45:定位到叶子 15,顺链表一路扫到 45
7.3 三大优势
1. 内部结点更小(不含数据)→ 一个磁盘块装更多指针 → 树更矮 → I/O 更少
2. 查找路径等长(必到叶子)→ 性能稳定可预测
3. 叶子成有序链表 → 范围查询 / 排序 / 分页 / 全表扫描极高效
7.4 B 树 vs B+ 树 总对照
| 维度 | B 树 | B+ 树 |
|---|---|---|
| 单点查找 | 可能中途命中,不等长 | 必到叶子,等长 |
| 范围查询 | 须中序遍历,麻烦 | 顺叶子链表扫,顺畅 |
| 全表扫描 | 遍历所有结点 | 只扫叶子链表 |
| 树高 | 较高 | 更矮 |
| 典型用途 | 文件系统索引(NTFS 等) | MySQL InnoDB、绝大多数数据库索引 |
7.5 为什么数据库几乎都选 B+ 树
假设一个磁盘块 4KB:
B 树某结点:[键 | 数据指针 | 孩子指针] × 几十个 → 几百个键/块
B+ 树内部结点:[键 | 孩子指针] × 几百个 → 上千个键/块
→ 同样的数据量,B+ 树能少一层甚至两层 → 一次查询少一两次磁盘 I/O
八、关系总结表
8.1 完整特性对照
| 结构 | 每点孩子数 | 值约束 | 平衡约束 | 平衡手段 | 树高上界 |
|---|---|---|---|---|---|
| 二叉树 | ≤ 2 | 无 | 无 | --- | n(退化) |
| BST | ≤ 2 | 左<根<右 | 无 | --- | n(退化) |
| AVL | ≤ 2 | 左<根<右 | BF ∈ {−1,0,1} | 旋转 | ≈ 1.44 log n |
| 红黑树 | ≤ 2 | 左<根<右 | 5 条颜色规则 | 变色 + 旋转 | ≤ 2 log(n+1) |
| B 树 | ≤ m | 结点内有序 | 所有叶子同层 | 分裂 + 合并 | log_⌈m/2⌉ n |
| B+ 树 | ≤ m | 结点内有序 | 所有叶子同层 | 分裂 + 合并 | log_⌈m/2⌉ n(更矮) |
8.2 继承关系链
二叉树 ⊃ BST ⊃ AVL
⊃ 红黑树(≈ 2-3-4 树)
B 树 ⊃ B+ 树
(B 树是"多路"分支的起点,与二叉树分支并列)
8.3 演进动机链
二叉树
│ 问题:不知道值往哪放 → 加排序约束
▼
BST
│ 问题:插入有序数据会退化成链表,O(log n) → O(n) → 加平衡约束
▼
AVL(严格平衡,查找快但写贵)/ 红黑树(近似平衡,写便宜)
│ 问题:树太高,磁盘 I/O 次数多 → 换维度:从"二叉"扩展到"多路"
▼
B 树
│ 问题:内部结点带数据,一块装不下几个键 → 数据下沉到叶子
▼
B+ 树(树更矮,范围查询更强 → 数据库首选)
九、各类"平衡手段"对照
| 结构 | 平衡靠什么 | 是否判断"局部朝向" | 触发修复的条件 |
|---|---|---|---|
| AVL | 旋转 | 是(看 BF 符号,判 LL/LR/RR/RL) | BF 达到 ±2 |
| 红黑树 | 变色 + 旋转 | 弱化(只看 z 与 p 是否同向) | 红红冲突且叔黑 |
| B 树 / B+ 树 | 分裂 + 合并 | 否 | 关键字数越界 |
最重要的一句话:
AVL 判断"高度符号";红黑树判断"方向 + 颜色";B 树判断"结点是否装满"。
三者目标相同(保持平衡),手段完全不同。
十、一张图看全家族

┌──────────────┐
│ 普通二叉树 │ 每点 ≤2 孩子,左右有序
│ 只规定形状 │
└──────┬───────┘
│ + 中序有序
┌──────▼───────┐
│ BST │ 左<根<右
│ 规定值的序 │ 中序 = 升序
└──────┬───────┘
│ + 平衡约束
┌───────────────┴───────────────┐
▼ ▼
┌───────────────┐ ┌───────────────┐
│ AVL 树 │ │ 红黑树 │
│ BF ∈ {−1,0,1} │ │ 5 条颜色规则 │
│ 严格平衡 │ │ 近似平衡 │
│ 旋转维持 │ │ 变色+旋转 │
│ 查多写少 │ │ 查少写多 │
└───────────────┘ └───────────────┘
══════════════════ 换维度:从二叉到多路 ══════════════════
┌──────────────┐
│ B 树 │ 每点 ≤m 孩子,所有叶子同层
│ 全结点存数据 │ 分裂/合并维持平衡
└──────┬───────┘
│ 数据下沉到叶子 + 叶子串链表
┌──────▼───────┐
│ B+ 树 │ 仅叶子存数据,内部纯索引