B树通过多键节点降低高度,减少磁盘I/O,适合数据库索引。其节点含有序键和子指针,支持对数时间查找、插入、删除。C++实现采用模板类定义固定大小键数组与子指针数组,核心操作包括二分查找、节点分裂与合并。插入时若节点满则提前分裂,确保路径畅通。实际系统中常扩展为B+树,节点对齐磁盘扇区,结合缓冲池、页号寻址与日志机制,提升持久化性能与恢复能力。

B树是一种自平衡的树数据结构,特别适合用于磁盘或其它直接存取辅助存储设备中的数据查找。由于C++在系统级编程和性能控制上的优势,实现一个B树来支持数据库或文件系统的索引操作非常合适。它的核心优势在于减少磁盘I/O次数——通过让每个节点包含多个键,降低树的高度。
理解B树的基本特性
B树满足以下关键性质:
- 每个节点最多有t(称为最小度数)个子节点
- 除根节点外,每个内部节点至少有⌈t/2⌉个子节点
- 根节点至少有两个子节点(除非它是叶子)
- 所有叶子节点在同一层
- 一个节点内的键有序排列,用于二分查找定位子树
这种设计使得即使数据量巨大,树的高度也能保持很小,从而将查找、插入、删除操作控制在对数时间复杂度内,且每次操作涉及的磁盘读写次数极少。
定义B树节点结构
在C++中,我们可以用类模板来泛化键类型,并管理节点中的键和子指针:
立即学习“C++免费学习笔记(深入)”;
templateclass BTreeNode { public: bool leaf; // 是否为叶子节点 int n; // 当前键的数量 T keys[2 * t - 1]; // 键数组(最多 2t-1 个) BTreeNode* children[2 * t]; // 子节点指针数组(最多 2t 个) BTreeNode() : leaf(true), n(0) { for (int i = 0; i zuojiankuohaophpcn 2 * t; ++i) children[i] = nullptr; } ~BTreeNode() { for (int i = 0; i zuojiankuohaophpcn n + 1; ++i) delete children[i]; }};
这里使用静态数组是为了避免动态分配带来的额外开销,适用于固定大小的块(如磁盘页)。实际数据库系统中会映射到页式存储结构。
实现B树的核心操作
B树的关键操作包括查找、分裂、插入和合并。以下是主要逻辑说明:
查找操作
从根开始,在当前节点中使用二分查找确定目标键的位置或应进入的子树:
templateBTreeNode * BTree ::search(BTreeNode * node, const T& k) { int i = 0; while (i < node->n && k > node->keys[i]) ++i; if (i zuojiankuohaophpcn node-youjiankuohaophpcnn && k == node-youjiankuohaophpcnkeys[i]) return node; if (node-youjiankuohaophpcnleaf) return nullptr; return search(node-youjiankuohaophpcnchildren[i], k);}
节点分裂
当节点满时(键数量达到 2t−1),需将其分裂为两个节点,并将中间键上移至父节点:
templatevoid BTree ::splitChild(BTreeNode * parent, int i) { BTreeNode * fullNode = parent->children[i]; BTreeNode * newNode = new BTreeNode (); newNode->leaf = fullNode->leaf; newNode->n = t - 1; // 复制后半部分键 for (int j = 0; j zuojiankuohaophpcn t - 1; ++j) newNode-youjiankuohaophpcnkeys[j] = fullNode-youjiankuohaophpcnkeys[j + t]; // 如果是非叶节点,复制子指针 if (!fullNode-youjiankuohaophpcnleaf) { for (int j = 0; j zuojiankuohaophpcn t; ++j) newNode-youjiankuohaophpcnchildren[j] = fullNode-youjiankuohaophpcnchildren[j + t]; } // 调整原节点数量 fullNode-youjiankuohaophpcnn = t - 1; // 将新节点插入父节点的孩子列表 for (int j = parent-youjiankuohaophpcnn; j youjiankuohaophpcn i; --j) parent-youjiankuohaophpcnchildren[j + 1] = parent-youjiankuohaophpcnchildren[j]; parent-youjiankuohaophpcnchildren[i + 1] = newNode; // 将中间键上移到父节点 for (int j = parent-youjiankuohaophpcnn - 1; j youjiankuohaophpcn= i; --j) parent-youjiankuohaophpcnkeys[j + 1] = parent-youjiankuohaophpcnkeys[j]; parent-youjiankuohaophpcnkeys[i] = fullNode-youjiankuohaophpcnkeys[t - 1]; parent-youjiankuohaophpcnn++;}
插入操作
插入总是试图加在叶子节点。若路径上有满节点,则提前分裂以保证后续插入不会溢出:
templatevoid BTree ::insertNonFull(BTreeNode * node, const T& k) { int i = node->n - 1; if (node-youjiankuohaophpcnleaf) { // 找到插入位置并右移元素 while (i youjiankuohaophpcn= 0 && k zuojiankuohaophpcn node-youjiankuohaophpcnkeys[i]) { node-youjiankuohaophpcnkeys[i + 1] = node-youjiankuohaophpcnkeys[i]; --i; } node-youjiankuohaophpcnkeys[i + 1] = k; node-youjiankuohaophpcnn++; } else { while (i youjiankuohaophpcn= 0 && k zuojiankuohaophpcn node-youjiankuohaophpcnkeys[i]) --i; ++i; if (node-youjiankuohaophpcnchildren[i]-youjiankuohaophpcnn == 2 * t - 1) { splitChild(node, i); if (k youjiankuohaophpcn node-youjiankuohaophpcnkeys[i]) ++i; } insertNonFull(node-youjiankuohaophpcnchildren[i], k); }}
与磁盘I/O优化结合的实际考虑
在真实数据库系统中,B树通常被改造为B+树,但基本思想一致。为了适配磁盘访问模式,可以做如下改进:
虽然上述代码运行在内存中,但它可作为底层索引模块的基础,配合页面管理器实现真正的持久化B树。
基本上就这些。只要掌握了分裂、合并和递归调整的思路,就能构建出高效可靠的磁盘友好型查找结构。实际应用中再叠加锁机制、并发控制和序列化功能即可用于生产级数据库系统。










