B树的插入操作

概述

B树的插入操作通过分裂满节点来维护B树性质。与二叉搜索树的插入不同,B树不能简单地在叶子节点添加新关键字——因为叶子节点可能已经满了。核心策略是提前分裂(proactive splitting):在从根向叶子遍历的过程中,一旦遇到满节点就立即分裂,确保父节点永远不会需要分裂时才发现子节点已满。

核心操作

1. B-TREE-SPLIT-CHILD:分裂满节点

分裂操作

B-TREE-SPLIT-CHILD():将节点 的第 个子节点 是满的,恰好有 个关键字)分裂为两个各含 个关键字的节点,并将 的中间关键字提升到 中。

具体步骤

  1. 创建新节点
  2. 的中间关键字 提升到 的第 个位置( 的关键字和子指针相应右移)
  3. 获得 的第 个关键字(共 个)
  4. 获得 的第 个子指针(共 个)
  5. 保留前 个关键字和前 个子指针
  6. 的关键字数 增加 的关键字数均为

分裂示意图

分裂前( 个关键字):

x: [... | c_i=y | ...]
y: [k_1 | k_2 | ... | k_{t-1} | k_t | k_{t+1} | ... | k_{2t-1}]
                          ↑ 中间关键字

分裂后:

x: [... | k_t | c_i=y | c_{i+1}=z | ...]
y: [k_1 | k_2 | ... | k_{t-1}]          z: [k_{t+1} | ... | k_{2t-1}]

关键性质:分裂操作后, 各有 个关键字,满足B树的最小关键字要求(非根节点至少 个关键字)。

2. B-TREE-INSERT-NONFULL:向非满节点插入

非满节点插入

B-TREE-INSERT-NONFULL():向非满节点 插入关键字

  1. 如果 是叶子节点:将 直接插入到 的正确位置(保持有序)
  2. 如果 是内部节点:找到 应该插入的子节点
    • 如果 已满:先对 执行 B-TREE-SPLIT-CHILD(),然后确定 应该进入分裂后的哪个子节点
    • 递归地将 插入到对应的子节点中

3. B-TREE-INSERT:完整插入流程

完整插入

B-TREE-INSERT():向B树 插入关键字

  1. 如果 已满():
    • 创建新节点 作为新根
    • 的唯一子节点设为
    • 执行 B-TREE-SPLIT-CHILD(),将 分裂为两个节点
    • 树的高度增加
  2. 调用 B-TREE-INSERT-NONFULL 插入树中

核心性质

1. 提前分裂策略的优势

CLRS采用的是提前分裂(也称”自顶向下分裂”)策略:

  • 优点:在向下遍历时就分裂满节点,保证父节点始终有空间接收提升的关键字,插入过程只需单次向下遍历
  • 替代方案:也可以采用”自底向上分裂”——先插入叶子,如果溢出再向上分裂。但这种方式在最坏情况下需要两次遍历(一次向下,一次向上)

2. 分裂的传播

  • 分裂可能向上传播:如果分裂导致父节点变满,父节点自身也可能需要分裂
  • 但由于提前分裂策略,父节点在被需要分裂之前就已经被处理了
  • 唯一例外是根节点分裂,此时树的高度增加

3. 复杂度分析

  • 磁盘I/O
    • 向下遍历: 次 DISK-READ
    • 分裂操作:每次分裂涉及 次 DISK-READ 和 DISK-WRITE
    • 总分裂次数不超过 (因为每次分裂只在一个层级发生)
  • CPU时间
    • 每个节点内部的搜索和移动操作为
    • 共访问 个节点

4. B树生长方向

B树从顶部向上生长(与二叉搜索树从底部向上生长不同):

  • 二叉搜索树:新节点总是作为叶子添加,树从下往上长
  • B树:新关键字插入叶子,但可能触发分裂使树从上往下长(根分裂导致高度增加)

参见