树的家族全景:从二叉树到 B+ 树

树的家族全景:从二叉树到 B+ 树

一条主线读完:二叉树 → BST → AVL / 红黑树 → B 树 → B+ 树 。

核心只有一句话:每一层都是在上一个结构上"加一条约束"或"换一种平衡手段",约束越多能力越专,平衡手段越松实现越稳。


目录

  1. 总览:关系地图
  2. 第一层:普通二叉树
  3. [第二层:二叉搜索树 BST](#第二层:二叉搜索树 BST)
  4. [第三层:AVL 树](#第三层:AVL 树)
  5. 第三层(并列):红黑树
  6. [第四层:B 树](#第四层:B 树)
  7. [第五层:B+ 树](#第五层:B+ 树)
  8. 关系总结表
  9. 各类"平衡手段"对照
  10. 一张图看全家族

一、总览:关系地图

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+ 树     │  仅叶子存数据,内部纯索引
相关推荐
垆边人似月.1 小时前
华为题:每日温度
数据结构·c++·算法
Peregrine91 小时前
数据结构 - > 树和二叉树
数据结构·算法
j7~2 小时前
【算法】专题四.前缀和:一维二维模板,配套 LeetCode 真题,结合同余定理、哈希前缀和,搞定子数组与子矩阵区间求和问题。
数据结构·c++·算法·前缀和·优选算法
启观川2 小时前
数据结构与算法 -第 3 章 常用算法-回溯算法
数据结构·笔记·python·算法
启观川11 小时前
数据结构与算法 -第 3 章 常用算法-动态规划
数据结构·笔记·python·算法
Logic10113 小时前
C语言/数据结构欧几里得算法题解:长方形切割最大正方形——贪心划分计数
c语言·数据结构·算法·贪心算法·时间复杂度·空间复杂度·欧几里得算法
flinn_small14 小时前
三分算法:原理、实现与实战应用
数据结构·算法·三分
INGNIGHT15 小时前
944 · 最大子矩阵(前缀和)
数据结构·c++·算法
All for pursuit.16 小时前
【动态规划-6】96.不同的二叉搜索树
数据结构·c++·算法·leetcode·动态规划