B 树与 B+ 树

2078 字
10 分钟
B 树与 B+ 树
Warning

含AI生成内容

B 树与 B+ 树#

概述#

B 树和 B+ 树是多路平衡查找树(Multi-way Balanced Search Tree),专为磁盘 I/O 设计的数据结构。折半查找和 BST/AVL 都是内存中的查找结构,而数据库索引动辄存储百万条记录,不可能全放内存。B 树/B+ 树的核心优势是:每个结点存储多个关键字,一次磁盘 I/O 读一个结点,大幅减少磁盘访问次数。

例如一棵 1001 阶 B 树,存储 10 亿个关键字只需要 3 层,最多 3 次磁盘访问即可完成查找。

相关概念页面:二叉排序树与 AVL 树红黑树查找算法

m 阶 B 树的定义#

B 树是 m 阶多路平衡查找树,其结点结构如下:

┌──┬────┬──┬────┬──┬─ ... ─┬──────┬──┐
│P₀│ K₁ │P₁│ K₂ │P₂│ │Kₙ │Pₙ│
└──┴────┴──┴────┴──┴─ ... ─┴──────┴──┘

其中 Kᵢ 为关键字(K₁ < K₂ < … < Kₙ),Pᵢ 为指向子树的指针,子树 Pᵢ 中所有关键字的值在 Kᵢ 和 Kᵢ₊₁ 之间。

B 树的性质#

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

性质描述
每个结点最多 m 棵子树即最多 m-1 个关键字
根结点子树数至少 2 棵(若非叶结点);关键字至少 1 个
非根非叶结点子树数至少 ⌈m/2⌉ 棵,即关键字至少 ⌈m/2⌉ - 1 个
所有叶结点在同一层叶结点是查找失败到达的空指针,不含任何信息
结点内关键字有序K₁ < K₂ < … < Kₙ

关键字个数范围(m 阶 B 树)#

结点类型子树个数关键字个数
根结点2 ~ m1 ~ m-1
非根非叶结点⌈m/2⌉ ~ m⌈m/2⌉ - 1 ~ m-1

B 树的高度#

含 n 个关键字的 m 阶 B 树:

  • 最小高度(每个结点尽可能满,每层 m-1 个关键字):h ≤ log_m(n+1)
  • 最大高度(每个结点尽可能少,根 1 关键字,其余 ⌈m/2⌉ - 1):h ≥ log_{⌈m/2⌉}((n+1)/2) + 1

易错:B 树的”叶结点”指最底层的外部空结点(NULL 指针),不含信息。最底层有关键字的结点称为”终端结点”。B 树”所有叶结点在同一层”指的是这些 NULL 空结点在同一层。

B 树的查找#

B 树的查找是在结点间纵向移动(磁盘 I/O)与在结点内横向比较(内存操作)交替进行的过程:

  1. 从根结点开始,将目标 key 与当前结点中的关键字顺序比较
  2. 若 key = Kᵢ,查找成功
  3. 若 Kᵢ < key < Kᵢ₊₁,沿指针 Pᵢ 进入子树(一次磁盘 I/O)
  4. 若到达叶结点(空指针),查找失败

磁盘 I/O 次数 = 查找路径上的结点数 = 树的高度 h。结点内比较在内存中完成,不涉及磁盘访问。

B 树的插入#

插入总是发生在最底层的非叶结点(终端结点)

  1. 查找确定插入位置
  2. 若插入后关键字数 ≤ m-1,直接插入,完成
  3. 若插入后关键字数 = m(溢出),进行结点分裂

结点分裂过程#

  1. 取中间关键字 K_{⌈m/2⌉}
  2. 将中间关键字上提到父结点
  3. 原结点分裂为左右两个结点(中间关键字左右两侧的关键字各成一结点)
  4. 若父结点也溢出,继续向上分裂
  5. 最坏情况分裂到根结点,树高度增加 1
分裂前(溢出): [K₁ K₂ K₃ K₄ K₅]
↑ 中间关键字 K₃ 上提
分裂后: 父结点 ← [..., K₃, ...]
/ \
[K₁ K₂] [K₄ K₅]

B 树长高的唯一方式是根结点分裂——这保证了所有叶结点始终在同一层。

B 树的删除#

删除后需保证每个结点的关键字数不低于 ⌈m/2⌉ - 1:

删除位置#

情况处理方式
删除关键字在终端结点直接删除,然后检查是否下溢
删除关键字在非终端结点用直接前驱(左子树最大值)或直接后继(右子树最小值)替换,转化为删除终端结点中的关键字

下溢处理#

当终端结点关键字数 < ⌈m/2⌉ - 1 时:

处理策略条件操作
借左兄弟左兄弟关键字数 > ⌈m/2⌉ - 1父结点关键字下移,左兄弟最大关键字上移
借右兄弟右兄弟关键字数 > ⌈m/2⌉ - 1父结点关键字下移,右兄弟最小关键字上移
合并左右兄弟都 = ⌈m/2⌉ - 1当前结点与一个兄弟合并,父结点中间关键字下移参与合并

合并可能导致父结点也下溢,需逐层向上处理,最坏情况合并到根结点,树高度减 1

借右兄弟示例(5 阶 B 树)#

[..., 35, ...]
/ \
[20] [40, 50, 60] 当前结点下溢,右兄弟富余
[..., 40, ...] 35 下移,40 上提
/ \
[20, 35] [50, 60] 恢复平衡

B+ 树#

B+ 树的特点#

B+ 树是 B 树的变体,专为范围查询优化。其核心特点:

  1. 所有数据存储在叶结点:内部结点仅保存索引(关键字副本),不存放实际数据
  2. 叶结点通过指针依次链接:形成一个有序链表,支持顺序遍历和范围查询
  3. 查找必须走到叶结点:无论目标是否在内部结点出现,都要查到叶子才能获取数据
  4. 内部结点的关键字个数 = 子树个数(B 树中是关键字个数 = 子树个数 - 1)

m 阶 B+ 树的结构#

[30 | 60] ← 内部结点(仅索引)
/ | \
[10|20] [30|50] [60|80] ← 内部结点(仅索引)
/ | \ / | \ / | \
[叶] → [叶] → [叶] → [叶] ← 叶结点(存数据,链表相连)

B 树与 B+ 树对比#

对比项B 树B+ 树
数据存储位置所有结点都存数据仅叶结点存数据
内部结点作用既是索引又存数据纯索引,不存数据
关键字个数n 个关键字对应 n+1 棵子树n 个关键字对应 n 棵子树
关键字是否重复关键字不重复内部结点的关键字在叶子中再次出现
查找路径可能在任意层命中必须查到叶结点
查找性能不稳定(最好 O(1))稳定(每次都 O(log n))
叶结点链表有,叶结点按顺序链接
范围查询需中序遍历,效率低沿叶结点链表扫描,效率高
内部结点扇出较小(同时存数据)较大(纯索引,容纳更多关键字)

为什么数据库索引用 B+ 树#

  1. 磁盘 I/O 更少:内部结点不存数据,同样大小的磁盘页可容纳更多关键字,树高更低
  2. 范围查询高效:定位到起始叶结点后,沿链表顺序扫描即可
  3. 查询性能稳定:每次查询都走到叶结点,路径长度一致
  4. 全表扫描友好:遍历叶结点链表即可完成全表顺序扫描

在 MySQL InnoDB 中,聚簇索引的叶结点直接存放整行数据,非聚簇索引的叶结点存放主键值(需回表查询)。

复杂度分析#

操作B 树B+ 树
查找(磁盘 I/O)O(log_m n)O(log_m n)
插入O(log_m n)O(log_m n)
删除O(log_m n)O(log_m n)
范围查询O(log n + k)(中序遍历)O(log n + k)(链表扫描,更优)

实际应用中 m 通常取较大值(几百到上千),使得树高 h 通常只有 3~4 层。

考研高频考点#

  • m 阶 B 树非根结点关键字数范围:⌈m/2⌉ - 1 ~ m-1(选择题/填空题高频)
  • 含 n 个关键字的 m 阶 B 树的最大高度公式推导(大题常考)
  • 插入时结点分裂过程、中间关键字上提(手动建树题)
  • 删除时借兄弟与合并操作的判断和执行(手动操作题)
  • B 树 vs B+ 树的区别(简答题/选择题必考)
  • B+ 树查找必须到叶结点(判断题高频陷阱)
  • B+ 树中内部结点关键字个数 = 子树个数(与 B 树不同)
  • B 树所有叶结点在同一层的原因(概念题)
  • 磁盘 I/O 与 B 树阶数选择的关系(理解题)
  • 给定关键字序列画出 B 树构造过程(综合应用题)

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

B 树与 B+ 树
https://lingluoa.icu/posts/b-tree/
作者
lingluoa
发布于
2026-07-07
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
lingluoa
Hello, I'm lingluoa.
公告
欢迎来到我的博客!不定期更新中。
文章目录
标签
站点统计
文章
53
分类
9
标签
75
总字数
175,771
运行时长
0
最后活动
0 天前
站点信息
构建平台
ESA Pages
博客版本
Firefly v6.15.6
文章许可
CC BY-NC-SA 4.0