网站备案说明合肥网络推广有限公司
2026/1/26 12:59:33 网站建设 项目流程
网站备案说明,合肥网络推广有限公司,成都网站建设推荐,六安分公司目录 ​编辑 前言 一、常见的搜索结构#xff1a;各有千秋却难承重任 1.1 顺序查找#xff1a;最简单却最低效 1.2 二分查找#xff1a;有序数据的 “快刀” 1.3 二叉搜索树#xff1a;动态数据的 “双刃剑” 1.4 平衡二叉树#xff08;AVL 树、红黑树#xff09;…目录​编辑前言一、常见的搜索结构各有千秋却难承重任1.1 顺序查找最简单却最低效1.2 二分查找有序数据的 “快刀”1.3 二叉搜索树动态数据的 “双刃剑”1.4 平衡二叉树AVL 树、红黑树追求平衡的 “努力家”1.5 哈希表理想中的 “O (1)” 却暗藏危机1.6 痛点总结与 B - 树的诞生二、B - 树概念平衡多叉树的 “黄金法则”2.1 什么是 B - 树2.2 B - 树的核心特性解读1“多叉” 是降低高度的关键2“平衡” 是保证性能稳定的基石3节点结构是 “磁盘友好” 的设计2.3 B - 树的节点结构详解三、B - 树的插入分析从原理到实操以 3 阶 B 树为例3.1 插入的核心规则3.2 分步拆解插入过程第一步插入 53树为空第二步插入 139根节点未满第三步插入 75根节点满需要分裂第四步插入 49 和 145叶子节点未满第五步插入 36叶子节点满分裂后向上插入第六步插入 101叶子节点满分裂后父节点也满继续分裂3.3 插入过程总结四、B - 树的插入实现C 代码详解4.1 设计思路4.2 节点结构定义4.3 B 树类定义4.4 查找功能实现Find 函数4.5 插入关键字辅助函数_InsertKey 函数4.6 插入主函数Insert 函数4.7 中序遍历验证_InOrder 函数4.8 测试代码4.9 测试结果4.10 代码优化与扩展1支持关键字不唯一2高阶 B 树优化3磁盘 IO 模拟总结前言在数据结构的世界里“搜索” 是永恒的核心命题。当我们面对几十条、几百条数据时顺序查找的 O (N) 复杂度或许还能应付但当数据量飙升到百万、亿级甚至需要存储在磁盘等外部设备时传统搜索结构就显得力不从心了。你是否好奇数据库是如何在海量数据中实现毫秒级检索的为什么平衡二叉树在磁盘场景下会 “水土不服”今天我们就来深入剖析一款专为外部存储优化的王者级数据结构 ——B - 树带你从原理到代码彻底搞懂它的设计哲学与实现细节。在正式进入 B - 树的世界之前我们先回顾一下常见的搜索结构看看它们各自的优缺点以及 B - 树是如何弥补这些缺陷的。下面就让我们正式开始吧一、常见的搜索结构各有千秋却难承重任在日常开发中我们接触过多种搜索结构它们在不同场景下各有表现但在海量数据的磁盘存储场景中都暴露出了明显的局限性。1.1 顺序查找最简单却最低效顺序查找是最基础的搜索方式对数据格式没有任何要求只需从头到尾逐一比对目标元素。这种方式的时间复杂度是 O (N)在数据量较小时比如几十条数据实现简单、开销小但当数据量达到万级以上效率就会急剧下降。想象一下在一本没有目录的百万页书籍中找一个关键词顺序查找的低效可想而知。1.2 二分查找有序数据的 “快刀”二分查找要求数据必须是有序的其核心思想是“折半缩小范围”时间复杂度优化到了 O (log₂N)。比如在 100 万条有序数据中查找目标最多只需 20 次比对就能找到效率远高于顺序查找。但二分查找的局限性也很明显一是仅适用于有序数据插入、删除操作会破坏有序性维护成本高二是依赖连续存储空间无法应对数据量超过内存的场景 —— 当数据存储在磁盘时二分查找的 “跳跃式访问” 会导致频繁的磁盘 IO而磁盘 IO 的速度比内存访问慢数百倍这会让其优势荡然无存。1.3 二叉搜索树动态数据的 “双刃剑”二叉搜索树无需数据预先有序插入、删除操作可以动态维护树结构其查找、插入、删除的时间复杂度理论上是 O (log₂N)。但二叉搜索树有一个致命缺陷结构不平衡。在最坏情况下比如插入的数据完全有序二叉搜索树会退化成单链表此时所有操作的时间复杂度都会退化为 O (N)性能甚至不如顺序查找。1.4 平衡二叉树AVL 树、红黑树追求平衡的 “努力家”为了解决二叉搜索树的不平衡问题平衡二叉树应运而生。AVL 树通过严格维护 “左右子树高度差不超过 1” 来保证平衡红黑树则通过颜色规则红节点的父节点必为黑节点、所有叶子节点到根节点的黑路径长度相同来维持近似平衡。它们都能将时间复杂度稳定在 O (log₂N)在内存中的表现十分优秀。但当数据存储在磁盘时平衡二叉树的缺陷就暴露无遗了树的高度过高。平衡二叉树是二叉树每个节点最多只有两个子节点树的高度大约是 log₂N。对于 1 亿条数据树的高度约为 27 层这意味着一次查找需要进行 27 次磁盘 IO。要知道磁盘 IO 是非常耗时的操作一次机械硬盘 IO 约 10ms27 次 IO 就需要 270ms这对于追求高并发、低延迟的系统来说是完全无法接受的。1.5 哈希表理想中的 “O (1)” 却暗藏危机哈希表是一种基于“键 - 值映射”的结构通过哈希函数将关键字映射到对应的存储位置理想情况下查找、插入、删除的时间复杂度都是 O (1)效率极高。但哈希表的局限性也不容忽视哈希冲突难以避免即使采用优秀的哈希函数也可能出现多个关键字映射到同一位置的情况此时需要通过链表法、开放寻址法等方式解决冲突极端情况下冲突会导致查找时间复杂度退化为 O (N)不支持范围查询哈希表的存储是无序的无法高效地进行 “查找大于 x 且小于 y 的所有数据” 这类范围查询操作依赖内存空间哈希表需要连续的内存空间来存储数据当数据量超过内存时无法高效扩展。1.6 痛点总结与 B - 树的诞生通过对以上搜索结构的分析我们可以总结出海量磁盘数据存储的核心痛点树结构高度过高导致磁盘 IO 次数过多无法高效支持动态插入、删除和范围查询极端场景下性能不稳定。为了解决这些问题1970 年R.Bayer 和 E.mccreight 提出了一种适合外部查找的平衡多叉树 ——B 树有些文献中写作 B - 树注意不要误读为 “B 减树”。B 树的核心设计思想是降低树的高度通过 “多叉” 结构让每个节点存储多个关键字和子节点指针从而大幅减少树的高度进而减少磁盘 IO 次数。对于 1 亿条数据若采用 100 阶的 B 树树的高度仅为 3 层log₅₀1 亿≈3.6只需 3 次磁盘 IO 就能完成查找效率提升了近 10 倍二、B - 树概念平衡多叉树的 “黄金法则”2.1 什么是 B - 树B 树是一种平衡的多路搜索树适用于外部存储设备如磁盘。它的 “平衡” 意味着所有叶子节点都在同一层“多路” 意味着每个节点可以有多个子节点超过两个。官方定义一棵 m 阶m2m 表示节点最多有 m 个子节点的 B 树可以是空树或者满足以下所有性质的树根节点的特殊规则根节点至少有两个子节点如果树非空最多有 m 个子节点分支节点的结构每个分支节点包含 k-1 个关键字和 k 个孩子其中 ceil (m/2) ≤ k ≤ mceil 是向上取整函数。例如 m33 阶 B 树ceil (3/2)2所以每个分支节点的子节点数 k 满足 2≤k≤3对应的关键字数为 1≤n≤2叶子节点的结构每个叶子节点包含 k-1 个关键字其中 ceil (m/2) ≤k ≤m与分支节点的子节点数范围相同且所有叶子节点都在同一层关键字的有序性每个节点中的关键字从小到大排列即 K₁K₂...Kₙn 为节点中关键字的个数子节点的值域划分节点中的 k-1 个关键字恰好是 k 个孩子包含的元素的值域划分。若节点的结构为 (A₀,K₁,A₁,K₂,A₂,...,Kₙ,Aₙ)则A₀所指子树的所有关键字都小于 K₁Aᵢ所指子树的所有关键字都大于 Kᵢ且小于 Kᵢ₊₁1≤i≤n-1Aₙ所指子树的所有关键字都大于 Kₙ关键字个数限制每个节点的关键字个数 n 满足 ceil (m/2)-1 ≤n ≤m-1。例如 m3 时ceil (3/2)-11m-12所以每个节点的关键字数 n 满足 1≤n≤2。2.2 B - 树的核心特性解读1“多叉” 是降低高度的关键B 树通过 “多叉” 结构让每个节点承载更多的关键字和子节点指针从而大幅降低树的高度。例如对于 m1024 的 1024 阶 B 树每个节点最多可以存储 1023 个关键字和 1024 个子节点指针。此时树的高度 h 满足h ≤ log_{ceil (m/2)} N N 为总关键字数对于 N620 亿个关键字ceil (1024/2)512log₅₁₂620 亿≈log₅₁₂(6.2×10¹⁰)≈log₂(6.2×10¹⁰)/log₂512≈35.8/9≈4即树的高度仅为 4 层。这意味着即使是 620 亿条数据最多只需 4 次磁盘 IO 就能找到目标这在磁盘存储场景中是革命性的提升。2“平衡” 是保证性能稳定的基石B 树要求所有叶子节点都在同一层这意味着从根节点到任何叶子节点的路径长度都相同。这种严格的平衡保证了查找、插入、删除操作的时间复杂度稳定在 O (logₘN)不会出现类似二叉搜索树的性能退化问题。3节点结构是 “磁盘友好” 的设计B 树的节点结构多个关键字 多个子节点指针非常适合磁盘存储。磁盘的读写单位是 “块”通常为 4KB、8KBB 树的节点大小可以设计为与磁盘块大小一致这样每次读取一个节点就只需一次磁盘 IO最大化利用了磁盘的块存储特性。2.3 B - 树的节点结构详解根据 B 树的定义每个节点的结构可以表示为(n, A₀, K₁, A₁, K₂, A₂, ..., Kₙ, Aₙ)其中各部分的含义n节点中关键字的个数满足 ceil (m/2)-1 ≤n ≤m-1Kᵢ1≤i≤n关键字且 K₁K₂...KₙAᵢ0≤i≤n指向子树根节点的指针且 Aᵢ所指子树的所有关键字都小于 Kᵢ₊₁Aₙ所指子树的所有关键字都大于 Kₙ特别注意子节点指针的个数永远比关键字个数多 1n 个关键字对应 n1 个子节点指针这是 B 树节点结构的核心特点。例如3 阶 B 树的节点m3关键字个数 n 满足 1≤n≤2ceil (3/2)-11m-12子节点指针个数为 n1即 2≤Aᵢ个数≤3。当 n2 时节点结构为 (A₀, K₁, A₁, K₂, A₂)其中A₀子树的关键字 K₁K₁ A₁子树的关键字 K₂A₂子树的关键字 K₂。这种结构清晰地划分了子树的关键字值域为查找和插入操作提供了明确的方向。三、B - 树的插入分析从原理到实操以 3 阶 B 树为例B 树的插入操作需要遵循两个核心原则一是保证插入后关键字有序二是保证 B 树的所有性质不被破坏节点关键字个数不超标、所有叶子节点在同一层。为了让分析更直观我们以3 阶 B 树m3每个节点最多 2 个关键字、3 个子节点最少 1 个关键字、2 个子节点为例详细拆解插入过程。插入的序列为{53, 139, 75, 49, 145, 36, 101}。3.1 插入的核心规则插入位置必在叶子节点B 树的插入操作只能在叶子节点进行这是为了保证所有叶子节点在同一层避免破坏树的平衡性插入后检测节点合法性插入关键字后若节点的关键字个数 n m-13 阶 B 树中 m-12即 n≤2则插入成功若 n m-1即节点满了则需要对节点进行分裂分裂后向上传递节点分裂后中间的关键字需要向上插入到父节点中若父节点也满了则继续分裂直到根节点根节点分裂后会生成新的根节点树的高度加 1。3.2 分步拆解插入过程第一步插入 53树为空树为空时直接创建根节点将 53 插入到根节点中此时根节点的关键字个数 n1满足 1≤n≤2插入成功。树的结构[53]第二步插入 139根节点未满从根节点开始查找插入位置139 53所以插入到 53 的右侧插入后根节点的关键字为 [53, 139]n2满足最大值限制插入成功。树的结构[53, 139]第三步插入 75根节点满需要分裂查找插入位置75 介于 53 和 139 之间插入到根节点中此时根节点的关键字为 [53, 75, 139]n3超过 m-12需要分裂分裂流程核心步骤找到节点的中间位置3 阶 B 树的中间位置为 mid (m-1)/2 1索引从 0 开始中间关键字为 75创建新节点将中间位置右侧的关键字139和对应的子节点指针此处无子节点搬移到新节点中将中间关键字75向上插入到父节点中此时父节点为空所以 75 成为新的根节点建立新根节点与原节点[53]、新节点[139]的父子关系。分裂后的树结构[75] / \ [53] [139]第四步插入 49 和 145叶子节点未满插入 49查找插入位置从根节点 75 开始49 75进入左子节点 [53]49 53插入到 53 的左侧此时左子节点的关键字为 [49, 53]n2满足限制插入成功。插入 145查找插入位置从根节点 75 开始145 75进入右子节点 [139]145 139插入到 139 的右侧此时右子节点的关键字为 [139, 145]n2满足限制插入成功。插入后的树结构[75] / \ [49, 53] [139, 145]第五步插入 36叶子节点满分裂后向上插入查找插入位置从根节点 75 开始36 75进入左子节点 [49, 53]36 49插入到 49 的左侧此时左子节点的关键字为 [36, 49, 53]n3超过限制需要分裂分裂左子节点 [36, 49, 53]中间位置 mid1中间关键字为 49创建新节点将中间右侧的关键字53搬移到新节点中原节点剩余关键字 [36]将中间关键字 49 向上插入到父节点 [75] 中父节点插入 49 后关键字为 [49, 75]n2满足限制插入成功。插入后的树结构[49, 75] / | \ [36] [53] [139, 145]第六步插入 101叶子节点满分裂后父节点也满继续分裂查找插入位置从根节点 [49, 75] 开始101 75进入右子节点 [139, 145]101 139插入到 139 的左侧此时右子节点的关键字为 [101, 139, 145]n3超过限制需要分裂分裂右子节点[101, 139, 145]中间位置 mid1中间关键字为 139创建新节点将中间右侧的关键字145搬移到新节点中原节点剩余关键字 [101]将中间关键字 139 向上插入到父节点 [49, 75] 中父节点插入 139 后关键字为 [49, 75, 139]n3超过 m-12需要分裂父节点分裂父节点根节点[49, 75, 139]中间位置 mid1中间关键字为 75创建新节点将中间右侧的关键字139搬移到新节点中原节点剩余关键字 [49]父节点是根节点分裂后生成新的根节点将中间关键字 75 插入到新根节点中建立新根节点与原节点[49]、新节点[139]的父子关系原右子节点分裂后的两个节点 [101] 和 [145]分别作为新节点 [139] 的左、右子节点。最终的树结构[75] / \ [49] [139] / \ / \ [36] [53] [101] [145]3.3 插入过程总结B 树的插入操作可以概括为“查找插入位置→插入关键字→检测节点是否满→满则分裂→向上传递中间关键字”的循环过程直到满足 B 树的所有性质为止。核心要点是插入位置始终在叶子节点保证叶子节点在同一层节点分裂是维持 B 树平衡的关键分裂后中间关键字向上传递确保父节点的关键字有序且个数合法根节点分裂会导致树的高度加 1但由于所有叶子节点都在同一层树的平衡性依然得到保证。四、B - 树的插入实现C 代码详解理解了 B 树的插入原理后我们来动手实现一个通用的 B 树模板类。本次实现采用 C 语言支持任意可比较的关键字类型默认实现 3 阶 B 树可通过模板参数修改阶数。4.1 设计思路节点结构设计每个节点需要存储关键字数组、子节点指针数组、父节点指针和有效关键字个数查找功能实现提供Find函数查找目标关键字的位置若未找到则返回其应插入的叶子节点和插入索引插入关键字辅助函数提供_InsertKey函数在指定节点的指定位置插入关键字和对应的子节点指针插入主函数实现 “查找→插入→检测→分裂→向上传递” 的完整流程验证函数通过中序遍历验证 B 树的关键字是否有序中序遍历结果有序是 B 树插入正确的必要条件。4.2 节点结构定义首先我们要定义 B 树的节点结构采用模板类实现模板参数 K 为关键字类型M 为 B 树的阶数默认 M3。#include iostream #include utility #include cassert using namespace std; // 模板参数K关键字类型MB树的阶数默认3阶 templateclass K, int M 3 struct BTreeNode { K _keys[M]; // 存储关键字最多M-1个有效关键字 BTreeNodeK, M* _pSub[M 1]; // 存储子节点指针最多M个比关键字多1个 BTreeNodeK, M* _pParent; // 父节点指针分裂时需要向上插入 size_t _size; // 当前节点的有效关键字个数 // 构造函数初始化所有指针为nullptr有效关键字个数为0 BTreeNode() : _pParent(nullptr) , _size(0) { for (size_t i 0; i M; i) { _pSub[i] nullptr; } } };节点结构说明_keys [M]由于 B 树的每个节点最多有 M-1 个关键字所以数组大小定义为 M预留一个位置方便插入时临时存储_pSub [M1]子节点指针数组的大小为 M1因为 n 个关键字对应 n1 个子节点指针最多 M 个关键字实际最多 M-1 个有效对应 M1 个子节点指针_pParent父节点指针用于节点分裂后向上插入中间关键字_size有效关键字个数范围为 [ceil (M/2)-1, M-1]。4.3 B 树类定义B 树类包含根节点指针以及查找、插入、中序遍历等核心成员函数。templateclass K, int M 3 class BTree { typedef BTreeNodeK, M Node; public: // 构造函数初始化根节点为nullptr BTree() : _pRoot(nullptr) {} // 插入关键字 bool Insert(const K key); // 中序遍历验证B树是否有序 void InOrder() { _InOrder(_pRoot); cout endl; } private: // 中序遍历辅助函数递归实现 void _InOrder(Node* pRoot); // 查找关键字返回值为pair所在节点, 关键字在节点中的索引未找到则索引为-1 pairNode*, int Find(const K key); // 在指定节点中插入关键字和对应的子节点指针 void _InsertKey(Node* pCur, const K key, Node* pSub); private: Node* _pRoot; // B树的根节点指针 };4.4 查找功能实现Find 函数Find 函数的功能是查找目标关键字 key若找到返回包含该关键字的节点和其在节点中的索引若未找到返回该关键字应插入的叶子节点和索引 - 1。查找过程遵循 B 树的关键字值域划分规则从根节点开始在当前节点的关键字数组中顺序查找也可优化为二分查找根据比较结果进入对应的子节点直到找到目标或到达叶子节点。templateclass K, int M pairtypename BTreeK, M::Node*, int BTreeK, M::Find(const K key) { Node* pCur _pRoot; Node* pParent nullptr; size_t i 0; while (pCur) { i 0; // 在当前节点的关键字中查找找到则返回 while (i pCur-_size) { if (key pCur-_keys[i]) { // 找到返回节点指针和索引i return make_pair(pCur, i); } else if (key pCur-_keys[i]) { // 关键字小于当前位置的关键字进入左子节点 break; } else { // 关键字大于当前位置的关键字继续向右查找 i; } } // 未在当前节点找到记录父节点进入第i个子节点 pParent pCur; pCur pCur-_pSub[i]; } // 到达叶子节点仍未找到返回父节点插入位置和索引-1 return make_pair(pParent, -1); }查找函数优化由于节点中的关键字是有序的当节点的关键字个数较多时如高阶 B 树可以将顺序查找改为二分查找进一步提升查找效率。二分查找版本的实现如下// 优化版Find函数二分查找 templateclass K, int M pairtypename BTreeK, M::Node*, int BTreeK, M::Find(const K key) { Node* pCur _pRoot; Node* pParent nullptr; while (pCur) { int left 0; int right pCur-_size - 1; int pos -1; // 二分查找当前节点的关键字 while (left right) { int mid (left right) / 2; if (key pCur-_keys[mid]) { return make_pair(pCur, mid); } else if (key pCur-_keys[mid]) { right mid - 1; } else { left mid 1; pos mid; // 记录最后一个小于key的位置 } } // 确定进入哪个子节点pos1若pos-1则进入第0个子节点 int i pos 1; pParent pCur; pCur pCur-_pSub[i]; } return make_pair(pParent, -1); }4.5 插入关键字辅助函数_InsertKey 函数_InsertKey函数的功能是在指定节点pCur的指定位置插入关键字key和对应的子节点指针pSub插入过程需保持节点内关键字的有序性类似插入排序。templateclass K, int M void BTreeK, M::_InsertKey(Node* pCur, const K key, Node* pSub) { // 从后往前移动元素为新关键字腾出位置 int end pCur-_size - 1; while (end 0 key pCur-_keys[end]) { // 关键字后移 pCur-_keys[end 1] pCur-_keys[end]; // 子节点指针同步后移子节点指针比关键字多1个所以是end2 pCur-_pSub[end 2] pCur-_pSub[end 1]; --end; } // 插入新关键字和子节点指针 pCur-_keys[end 1] key; pCur-_pSub[end 2] pSub; // 如果子节点不为空更新子节点的父节点指针 if (pSub) { pSub-_pParent pCur; } // 更新节点的有效关键字个数 pCur-_size; }函数说明插入时从节点的末尾开始向前移动元素直到找到合适的插入位置确保插入后关键字仍有序子节点指针需要同步后移因为子节点指针的个数比关键字多 1 个所以移动的索引是 end2若插入的是分裂后的子节点pSub 不为空需要更新 pSub 的父节点指针使其指向当前节点 pCur。4.6 插入主函数Insert 函数Insert函数是 B 树插入操作的核心需要实现 “查找→插入→检测→分裂→向上传递” 的完整流程。templateclass K, int M bool BTreeK, M::Insert(const K key) { // 情况1树为空直接创建根节点并插入关键字 if (_pRoot nullptr) { _pRoot new Node; _pRoot-_keys[0] key; _pRoot-_size 1; return true; } // 情况2树非空查找插入位置 pairNode*, int ret Find(key); // 关键字已存在插入失败B树默认关键字唯一 if (ret.second ! -1) { cout 关键字 key 已存在插入失败 endl; return false; } K insertKey key; // 待插入的关键字可能是分裂后的中间关键字 Node* insertSub nullptr; // 待插入的子节点分裂后的新节点 Node* pCur ret.first; // 插入位置的叶子节点 while (true) { // 插入关键字到当前节点 _InsertKey(pCur, insertKey, insertSub); // 检测当前节点的关键字个数是否合法n M-1 if (pCur-_size M) { // 合法插入成功 return true; } // 节点满了需要分裂 // 步骤1计算中间位置3阶B树mid15阶B树mid2即(m-1)/2 int mid M / 2; // 因为M是阶数M-1是最大关键字个数mid(M-1)/2整数除法 // 步骤2创建新节点将中间位置右侧的关键字和子节点指针搬移到新节点 Node* newNode new Node; size_t j 0; // 搬移关键字mid1到pCur-_size-1 for (size_t i mid 1; i pCur-_size; i) { newNode-_keys[j] pCur-_keys[i]; newNode-_pSub[j] pCur-_pSub[i]; // 若子节点不为空更新子节点的父节点为新节点 if (newNode-_pSub[j]) { newNode-_pSub[j]-_pParent newNode; } j; } // 搬移最后一个子节点指针子节点指针比关键字多1个 newNode-_pSub[j] pCur-_pSub[pCur-_size]; if (newNode-_pSub[j]) { newNode-_pSub[j]-_pParent newNode; } // 更新新节点的有效关键字个数 newNode-_size j; // 步骤3更新原节点pCur的有效关键字个数移除中间及右侧的关键字 pCur-_size mid; // 步骤4将中间关键字和新节点向上插入到父节点 insertKey pCur-_keys[mid]; // 中间关键字 insertSub newNode; // 新节点 // 步骤5判断当前节点是否为根节点 if (pCur _pRoot) { // 根节点分裂创建新的根节点插入中间关键字和两个子节点 Node* newRoot new Node; newRoot-_keys[0] insertKey; newRoot-_pSub[0] pCur; newRoot-_pSub[1] newNode; newRoot-_size 1; // 更新原节点和新节点的父节点为新根节点 pCur-_pParent newRoot; newNode-_pParent newRoot; // 更新B树的根节点 _pRoot newRoot; return true; } else { // 非根节点分裂继续向上插入到父节点 pCur pCur-_pParent; } } }关键步骤解析树为空处理直接创建根节点插入关键字后返回查找插入位置调用 Find 函数找到叶子节点和插入位置若关键字已存在则返回失败插入关键字调用_InsertKey 函数在叶子节点插入关键字节点分裂判断若插入后节点关键字个数等于 M满了则进行分裂节点分裂流程计算中间位置 mid对于 m 阶 B 树mid(m-1)/2整数除法创建新节点将 mid 右侧的关键字和子节点指针搬移到新节点更新原节点的有效关键字个数为 mid将中间关键字和新节点向上插入到父节点根节点分裂处理若分裂的是根节点创建新的根节点插入中间关键字和两个子节点树的高度加 1。4.7 中序遍历验证_InOrder 函数B 树的中序遍历结果是有序的从小到大因此可以通过中序遍历验证插入操作是否正确。中序遍历的规则是先遍历第 0 个子节点再访问第 0 个关键字接着遍历第 1 个子节点再访问第 1 个关键字以此类推最后遍历第 n 个子节点n 为有效关键字个数。templateclass K, int M void BTreeK, M::_InOrder(Node* pRoot) { if (pRoot nullptr) { return; } // 中序遍历左子树 → 关键字 → 右子树 for (size_t i 0; i pRoot-_size; i) { _InOrder(pRoot-_pSub[i]); // 遍历第i个子节点 cout pRoot-_keys[i] ; // 访问第i个关键字 } // 遍历最后一个子节点第size个子节点 _InOrder(pRoot-_pSub[pRoot-_size]); }4.8 测试代码为了验证 B 树的插入功能是否正确我们编写测试代码插入序列 {53, 139, 75, 49, 145, 36, 101}并通过中序遍历查看结果是否有序。int main() { // 定义3阶B树 BTreeint, 3 btree; // 插入测试数据 int keys[] {53, 139, 75, 49, 145, 36, 101}; for (auto key : keys) { btree.Insert(key); } // 中序遍历预期结果36 49 53 75 101 139 145 cout B树中序遍历结果; btree.InOrder(); // 测试插入重复关键字 btree.Insert(75); return 0; }4.9 测试结果运行测试代码后输出结果如下B树中序遍历结果36 49 53 75 101 139 145 关键字75已存在插入失败中序遍历结果为有序序列说明插入操作正确重复关键字插入失败符合 B 树的关键字唯一要求。4.10 代码优化与扩展1支持关键字不唯一默认实现中 B 树的关键字是唯一的若需要支持重复关键字可以修改 Find 函数和 Insert 函数Find函数找到关键字后不返回继续查找直到叶子节点插入到重复关键字的右侧Insert函数移除 “关键字已存在则返回失败” 的判断。2高阶 B 树优化对于高阶 B 树如 M1024节点的关键字个数较多_InsertKey函数中的顺序移动元素会影响效率。可以通过以下方式优化采用二分查找确定插入位置减少比较次数使用数组或 vector 存储关键字和子节点指针支持高效的插入操作但 vector 的动态扩容可能带来开销需权衡。3磁盘 IO 模拟在实际应用中B 树的节点存储在磁盘上需要模拟磁盘 IO 操作将节点的大小设计为与磁盘块大小一致如 4KB实现节点的读写函数将节点数据写入磁盘或从磁盘读取到内存在 Find 和 Insert 函数中当访问子节点时触发磁盘 IO 操作读取子节点到内存。总结B 树作为一种专为外部存储优化的平衡多叉树其核心优势在于低高度、高平衡、磁盘友好完美解决了海量数据存储场景下的检索效率问题。B树的设计充满了智慧是数据结构与实际应用场景深度结合的典范。希望本文能帮助你彻底掌握 B 树的核心知识在未来的开发和面试中脱颖而出如果你有任何疑问或建议欢迎在评论区留言交流

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询