B树的插入操作
概述
B树的插入操作通过分裂满节点来维护B树性质。与二叉搜索树的插入不同,B树不能简单地在叶子节点添加新关键字——因为叶子节点可能已经满了。核心策略是提前分裂(proactive splitting):在从根向叶子遍历的过程中,一旦遇到满节点就立即分裂,确保父节点永远不会需要分裂时才发现子节点已满。
核心操作
1. B-TREE-SPLIT-CHILD:分裂满节点
分裂操作
B-TREE-SPLIT-CHILD():将节点 的第 个子节点 ( 是满的,恰好有 个关键字)分裂为两个各含 个关键字的节点,并将 的中间关键字提升到 中。
具体步骤:
- 创建新节点
- 的中间关键字 提升到 的第 个位置( 的关键字和子指针相应右移)
- 获得 的第 到 个关键字(共 个)
- 获得 的第 到 个子指针(共 个)
- 保留前 个关键字和前 个子指针
- 的关键字数 增加 , 和 的关键字数均为
分裂示意图
分裂前( 有 个关键字):
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():向非满节点 插入关键字 。
- 如果 是叶子节点:将 直接插入到 的正确位置(保持有序)
- 如果 是内部节点:找到 应该插入的子节点
- 如果 已满:先对 执行 B-TREE-SPLIT-CHILD(),然后确定 应该进入分裂后的哪个子节点
- 递归地将 插入到对应的子节点中
3. B-TREE-INSERT:完整插入流程
完整插入
B-TREE-INSERT():向B树 插入关键字 。
- 设
- 如果 已满():
- 创建新节点 作为新根
- 的唯一子节点设为
- 对 执行 B-TREE-SPLIT-CHILD(),将 分裂为两个节点
- 树的高度增加
- 调用 B-TREE-INSERT-NONFULL 将 插入树中
核心性质
1. 提前分裂策略的优势
CLRS采用的是提前分裂(也称”自顶向下分裂”)策略:
- 优点:在向下遍历时就分裂满节点,保证父节点始终有空间接收提升的关键字,插入过程只需单次向下遍历
- 替代方案:也可以采用”自底向上分裂”——先插入叶子,如果溢出再向上分裂。但这种方式在最坏情况下需要两次遍历(一次向下,一次向上)
2. 分裂的传播
- 分裂可能向上传播:如果分裂导致父节点变满,父节点自身也可能需要分裂
- 但由于提前分裂策略,父节点在被需要分裂之前就已经被处理了
- 唯一例外是根节点分裂,此时树的高度增加
3. 复杂度分析
- 磁盘I/O: 次
- 向下遍历: 次 DISK-READ
- 分裂操作:每次分裂涉及 次 DISK-READ 和 DISK-WRITE
- 总分裂次数不超过 (因为每次分裂只在一个层级发生)
- CPU时间:
- 每个节点内部的搜索和移动操作为
- 共访问 个节点
4. B树生长方向
B树从顶部向上生长(与二叉搜索树从底部向上生长不同):
- 二叉搜索树:新节点总是作为叶子添加,树从下往上长
- B树:新关键字插入叶子,但可能触发分裂使树从上往下长(根分裂导致高度增加)