完整教程:超详细图解 B 树:从原理到插入删除,手把手构造全过程

超详细图解 B 树:从原理到插入删除,手把手构造全过程

B 树不是二叉树,却比红黑树更“稳”——它是数据库和文件系统的幕后英雄。

如果你用过 MySQL、MongoDB 或 Linux 文件系统,那你一定间接用过 B 树(B-Tree)。它不像红黑树那样常出现在算法题里,却是大规模数据存储的核心结构。

今天,我们就来彻底讲清楚:

  • 什么是 B 树?

  • 它为什么能高效支持磁盘 I/O?

  • 插入和删除时到底发生了什么?

  • 每一种边界情况,都配 ASCII 图示 + 例子!

  • 文末还有 从空树开始,一步步构造完整 B 树的全过程


一、B 树是什么?为什么需要它?

✅ 基本定义(以 m 阶 B 树为例):

一棵 m 阶 B 树 满足以下性质:

  1. 每个结点最多有 m - 1 个关键字,最多 m 个子树;

  2. 除根结点外,每个内部结点至少有 ⌈m/2⌉ - 1 个关键字;

  3. 根结点至少有 1 个关键字(除非树为空);

  4. 所有叶子结点在同一层(完美平衡);

  5. 结点内关键字有序,子树按区间划分。

举个例子:5 阶 B 树 → 每个结点最多 4 个关键字,非根最少 2 个。

为什么 B 树适合磁盘存储?

  • 磁盘读写以“块”为单位(如 4KB)

  • B 树高度低、分支多,一次 I/O 可读取大量关键字

  • 相比二叉树(高度 ≈ log₂N),B 树高度 ≈ logₘN(m 很大,如 100+)


二、B 树的插入操作:分裂是关键!

插入总是发生在叶子结点。如果插入后关键字数 > m−1,就分裂。

我们以 5 阶 B 树(m=5)为例:

  • 最多 4 个关键字

  • 分裂时:5 个关键字 → 上提第 3 个(⌈5/2⌉ = 3)


✅ 情况 1:插入后未溢出 → 直接插入

当前结点:

[10, 20, 30]

插入 25 → 排序后:

[10, 20, 25, 30]   ← 4个,合法

✅ 无需分裂。


✅ 情况 2:叶子溢出,父结点有空间

当前树:

        [50]
       /    \
[10,20,25,30]  [60,70]

插入 40 到左子树 → 左子变成 [10,20,25,30,40](5个,溢出!)

分裂左子树:

  • 关键字:[10,20,25,30,40]

  • 上提第 3 个:25

  • 左新:[10,20],右新:[30,40]

更新后树:

          [25, 50]
         /   |    \
[10,20]  [30,40]  [60,70]

✅ 父结点原为 [50],插入 25 后变成 [25,50](2个 ≤ 4),合法!


✅ 情况 3:连锁分裂(向上传播)

当前树(父结点已满):

        [20,30,40,50]
       /  /  |  \    \
[10] [25] [35] [45] [55,60]

在最左子树连续插入 6,7,8,9,10 后,左子变为 [6,7,8,9,10](溢出)

→ 分裂左子,上提 8
→ 父结点变成 [8,20,30,40,50](5个,也溢出!)

→ 再分裂父结点,上提 30
→ 创建新根

最终树:

            [30]
          /      \
     [8,20]    [40,50]
    /  |  \     /  |  \
[6,7][9,10][25][35][45][55,60]

✅ 树高从 2 增加到 3!


三、B 树的删除操作:比插入更复杂!

删除可能发生在内部或叶子结点。核心目标:保持关键字数 ≥ 下限(5阶中为2)。


✅ 情况 1:直接删除(仍 ≥ 下限)

结点:[10,20,30,40]
删除 30 → [10,20,40](3 ≥ 2)→ ✅


✅ 情况 2:从兄弟“借位”

当前树:

        [50]
       /    \
[10,20,30]  [60,70,80,90]

删除 30 → 左子 [10,20](合法)
再删 20 → [10],可借:

  • 父 50 下移

  • 右兄弟最小 60 上移

  • 左子变成 [10,50]

  • 右子变成 [70,80,90]

新树:

        [60]
       /    \
[10,50]   [70,80,90]

✅ 两边都 ≥2!


✅ 情况 3:合并(兄弟也穷)

当前树:

        [50]
       /    \
   [10]    [60]

删除 10 → 左子空(0 < 2)
右兄弟只有 1 个(<2),无法借 → 合并!

合并步骤:

  1. 把父结点 50 拿下来

  2. 合并左右子 + 50 → [10,50,60]

  3. 父结点变空 → 删除父结点

结果:

[10,50,60]

✅ 树高降低!


✅ 情况 4:删除内部结点

树:

        [30]
       /    \
[10,20]    [40,50]

要删除 30(内部结点)

→ 用后继 40 替换它
→ 在右子树中删除 40 → 右子变 [50](1 < 2)

兄弟 [10,20] 刚好下限,无法借 → 合并

合并后:

[10,20,40,50]

✅ 完成!


四、完整构造演示:从空树插入 10 个数

目标:5 阶 B 树,插入序列:
3, 7, 1, 8, 5, 10, 2, 9, 4, 6

Step 1–4:直接插入

[1,3,7,8]

Step 5:插入 5 → 溢出,分裂

    [5]
   /   \
[1,3] [7,8]

Step 6–8:插入 10,2,9

    [5]
   /   \
[1,2,3] [7,8,9,10]

Step 9:插入 4 → 左子满但未溢出

      [5]
     /   \
[1,2,3,4] [7,8,9,10]

Step 10:插入 6 → 右子溢出,分裂

右子变为 [6,7,8,9,10] → 上提 8

最终树:

        [5,8]
       /  |  \
[1,2,3,4] [6,7] [9,10]

✅ 所有结点关键字数合法,树高为 2!


结语

B 树的美,在于它的自平衡机制:

  • 插入 → 分裂 → 向上传播

  • 删除 → 借位/合并 → 向上传播

每一步都像搭积木,稳而不乱。

下次你看到数据库索引飞快查询百万数据,就知道——那是 B 树在默默分裂与合并。


 动手建议:用纸笔模拟一遍上述构造过程,你会对 B 树有质的飞跃!

❤️ 如果你觉得这篇推文帮你真正理解了 B 树,欢迎 点赞、在看、转发!
评论区留下你的疑问,我们一起拆解数据结构!


posted on 2026-02-16 11:31  ljbguanli  阅读(115)  评论(0)    收藏  举报