在大屏或电脑上体验更佳——动画和操作区域更宽敞。
输入

B+ 树

B+ 树把有序键分装在页中。内部节点用分隔键指路,叶节点保存记录键。所有叶节点深度相同,叶节点还按键序相连。点查询沿树下降;范围查询找到起点后沿叶链扫描。等值、范围查询及与索引顺序兼容的 ORDER BY 都能利用这种结构。

B+ 树如何工作

本课的阶数 m 指内部节点最多有 m 个孩子,因此稳定节点最多有 m−1 个键。三阶节点最多存两个键,第三个键触发分裂。

内部节点 [15,30] 的三个孩子分别负责 <15、15≤key<30、≥30。等于分隔键时走右侧,因为记录副本在右侧叶节点中。内部键只用于路由,不算另一条记录。

叶节点溢出时,分成左右两页,把右页的首键复制到父节点。该键仍留在右叶中。内部节点分裂时,把中间分隔键移入父节点,不在分裂后的两个内部节点中保留它。复制与移入承担不同职责,不能混用。

叶链也要改。先让新右叶的 next 指向旧后继,再让旧叶的 next 指向新右叶,否则可能丢失原来的后继。内部页分裂则要连同孩子指针一起划分。父节点收到分隔键后也可能溢出,分裂可逐层传播。根分裂会建立新根,所有叶节点的深度一起增加。

逐步演示

默认使用三阶树,操作如下:

insert 10, insert 20, insert 5, insert 15, insert 25, insert 30, search 25, range 9 27

  1. 插入 10、20,根叶为 [10,20]。
  2. 插入 5,[5,10,20] 溢出。分成 [5,10]、[20],复制 20 建立新根。
  3. 插入 15,左叶变成 [5,10,15],分成 [5,10]、[15]。新叶 [15] 先接住原后继 [20],再接到 [5,10] 后面。把 15 复制到根。
  4. 插入 25,右叶成为 [20,25]。
  5. 插入 30,右叶分成 [20,25]、[30],复制 30 到根。根的 [15,20,30] 溢出,把中间键 20 移入新根,两侧内部节点分别只保留 15、30。

最终结构为:

                 [20]
              /        \
            [15]       [30]
           /   \       /   \
      [5,10]  [15] [20,25] [30]
          └────→────→────→

叶链为 [5,10] -> [15] -> [20,25] -> [30]。每个记录键只在叶层出现一次;内部的 15、20、30 是分隔键。

查找 25:在根 20 处走右侧,在内部键 30 处走左侧,在叶 [20,25] 中二分查找,共访问三页。

查询闭区间 [9,27]:先下降到左叶,以 lower bound 找到 10。输出 10,沿链输出 15,再输出 20、25,最后到达 [30]。30 超过上界,停止。结果为 [10,15,20,25],叶间跳转三次。每个结果都重新从根查找,会失去叶链的连续扫描收益。

何时使用

B+ 树同时支持等值与有序范围查询。哈希索引适合等值查找,但不能靠哈希桶完成键序扫描。B+ 树的高扇出减少树高;真实数据库页通常能容纳远多于本课的孩子数,因此大量记录也可能只需几层。

复杂度

设记录数为 n,阶数为 m,返回记录数为 k。点查询访问 O(log_m n) 层页。不发生分裂的插入沿同一路径下降;分裂最多每层一次,页层级工作仍为 O(log_m n)。范围查询为 O(log_m n+k):定位起点,再输出结果。

这些界限以固定阶数的教学模型或页层级访问计数为前提。页内二分查找、键和指针搬移另有 CPU 成本;把 m 当变量时不能忽略它们。访问一页也不等于一次物理磁盘读取,页可能在缓存中。界面的阶数 3–5 用于展示分裂,不是生产配置建议。

边界情况

  • 空树只有根叶。查找返回未找到,范围结果为空。
  • 第一次叶分裂建立新根,从一层变成两层。
  • 模型只存唯一键。重复插入不改变树;真实索引可以借助记录标识等方式支持重复键。
  • 缺失键只需检查路由选中的候选叶,无须遍历全部叶。
  • 范围起点的 lower bound 已超过上界时,立即结束,不必沿链。
  • 范围超过最大键时,在叶链尾部的空 next 处结束。
  • 宽范围 range 1 99 返回默认插入的全部键。输出规模仍须计入 k,不能只计算树高。
  • 画布最多展示 12 条记录。达到上限后,插入按钮仍可点击并说明限制;这是绘图上限,不是算法容量。

常见错误

  • 把叶分隔键移走,导致记录消失。叶分裂必须复制右叶首键。
  • 在内部节点中保留已上移的中间键,破坏孩子区间划分。
  • 等于分隔键时走左侧,错过右叶中的记录。
  • 先覆盖旧 next,再试图读取旧后继,断开叶链。
  • 对范围内每个键都从根开始查找,丢掉一次下降后顺序扫描的优势。
  • 把示意页当成 PostgreSQL 或 MySQL 的字节布局。

模型范围

本课使用内存中的唯一整数键、固定阶数与溢出后立即分裂,未实现删除。它不模拟缓冲池、物理 I/O、填充率、压缩、重复键、并发锁闩、WAL、MVCC、恢复和磁盘格式。真实叶节点还可能保存记录内容或记录地址,扇出也更大。画布展示路由、分裂和叶链的约束。