news 2026/5/27 17:40:06

B树:数据库索引的高效基石

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
B树:数据库索引的高效基石

引言

在前面的树系列中,我们学习的 BST、AVL 树、红黑树都是二叉树——每个节点最多两个子节点。当数据量小、能全部放进内存时,二叉树足够高效。

但现实是:数据库和文件系统的索引数据动辄几十 GB,远远超出内存容量,必须存储在磁盘上。磁盘 I/O 的速度比内存慢了几十万倍,传统的二叉树高度太高,一次查找需要访问几十个磁盘页,完全不可接受。

B 树正是为此而生。它是一种多路平衡搜索树,每个节点可以存储多个键、拥有多个子节点,通过"矮胖"的结构大幅降低树的高度,从而减少磁盘 I/O 次数。

第一部分:B 树的基本概念

一、什么是 B 树

B 树(B-Tree)是一种自平衡的多路搜索树,由 Rudolf Bayer 和 Edward McCreight 于 1971 年提出。

B 树的定义(m 阶 B 树)

二、B 树节点的内部结构

三、B 树 vs 二叉树的高度对比

第二部分:B 树的查找

一、查找过程

B 树的查找和 BST 类似,区别在于每个节点内有多个键,需要在节点内找到正确的区间

二、查找代码

#define M 5 // B 树的阶 typedef struct BTreeNode { int keys[M - 1]; // 键数组(最多 M-1 个键) struct BTreeNode* children[M]; // 子节点指针数组(最多 M 个) int n; // 当前键的数量 int isLeaf; // 是否为叶子节点(1=叶子,0=内部节点) } BTreeNode; // 在节点 node 内查找 key // 找到返回 1 并设置 *pos 为 key 的下标 // 未找到返回 0 并设置 *pos 为应该进入的子节点下标 int searchInNode(BTreeNode* node, int key, int* pos) { int i = 0; while (i < node->n && key > node->keys[i]) { i++; } if (i < node->n && key == node->keys[i]) { *pos = i; return 1; // 找到了 } *pos = i; // 没找到,返回应进入的子节点下标 return 0; } // 在 B 树中查找 key BTreeNode* search(BTreeNode* root, int key) { if (root == NULL) return NULL; int pos; if (searchInNode(root, key, &pos)) { return root; // 在当前节点找到了 } if (root->isLeaf) { return NULL; // 叶子节点,不存在 } return search(root->children[pos], key); // 进入子节点继续找 }

第三部分:B 树的插入

一、插入策略

B 树的插入比二叉树复杂很多,因为节点有容量上限。核心策略:始终插入到叶子节点;如果节点满了就分裂

二、分裂过程(5 阶 B 树,M=5,每节点最多 4 键)

三、插入代码

// 分裂节点 node 的第 childIndex 个子节点(该子节点已满) void splitChild(BTreeNode* parent, int childIndex) { BTreeNode* child = parent->children[childIndex]; BTreeNode* newChild = (BTreeNode*)malloc(sizeof(BTreeNode)); newChild->isLeaf = child->isLeaf; int mid = (M - 1) / 2; // 中间键的位置 // 右半部分的键复制到新节点 newChild->n = child->n - mid - 1; for (int j = 0; j < newChild->n; j++) { newChild->keys[j] = child->keys[mid + 1 + j]; } // 如果不是叶子,复制子指针 if (!child->isLeaf) { for (int j = 0; j <= newChild->n; j++) { newChild->children[j] = child->children[mid + 1 + j]; } } child->n = mid; // 左半部分保留在原节点 // 父节点腾出位置,插入中间键 for (int j = parent->n; j > childIndex; j--) { parent->keys[j] = parent->keys[j - 1]; parent->children[j + 1] = parent->children[j]; } parent->keys[childIndex] = child->keys[mid]; parent->children[childIndex + 1] = newChild; parent->n++; } // 向非满节点插入 key(递归辅助函数) void insertNonFull(BTreeNode* node, int key) { int i = node->n - 1; if (node->isLeaf) { // 叶子节点:找到位置直接插入 while (i >= 0 && key < node->keys[i]) { node->keys[i + 1] = node->keys[i]; i--; } node->keys[i + 1] = key; node->n++; } else { // 内部节点:找到应该进入的子节点 while (i >= 0 && key < node->keys[i]) i--; i++; // 子节点下标 // 如果子节点满了,先分裂 if (node->children[i]->n == M - 1) { splitChild(node, i); if (key > node->keys[i]) i++; // 确定分裂后进入哪个子节点 } insertNonFull(node->children[i], key); } } // B 树插入主函数 BTreeNode* insert(BTreeNode* root, int key) { if (root == NULL) { BTreeNode* node = (BTreeNode*)malloc(sizeof(BTreeNode)); node->keys[0] = key; node->n = 1; node->isLeaf = 1; return node; } if (root->n == M - 1) { // 根满了,创建新根 BTreeNode* newRoot = (BTreeNode*)malloc(sizeof(BTreeNode)); newRoot->isLeaf = 0; newRoot->n = 0; newRoot->children[0] = root; splitChild(newRoot, 0); insertNonFull(newRoot, key); return newRoot; } insertNonFull(root, key); return root; }

第四部分:B 树的删除

B 树的删除是最复杂的操作,核心原则是:删除后每个节点仍满足最少键数的要求(根除外,内部节点至少 ⌈M/2⌉-1 个键)。

一、删除的三种情况

二、删除后的"借"与"合并"

当节点删除后键数不足最少要求时

第五部分:B 树的性能分析

操作时间复杂度说明
查找O(log n)每层在节点内二分查找 O(log m) + 树高 O(logₘ n) = O(log n)
插入O(log n)可能向上分裂,最坏到根
删除O(log n)可能借键或合并,最坏到根
空间O(n)每节点有 M-1 个键和 M 个指针

总结

一、B 树核心要点

要点内容
核心思想多路搜索,"矮胖"结构减少磁盘 I/O
节点结构键 + 子指针,键在节点内有序
插入总是插入叶子,满了就分裂,可能向上递归
删除删内部节点用前后继替代,删叶子可能借或合并
平衡性所有叶子在同一层
应用数据库索引(B+ 树变体)、文件系统

二、B 树 vs 二叉树

对比二叉树B 树
子节点数2M 个
树高高(log₂n)矮(logₘn)
磁盘 I/O
适用场景内存磁盘

三、一句话记忆

B 树是多路平衡搜索树,每个节点存多个键、有多个子节点,通过"矮胖"结构大幅降低树高,从而减少磁盘 I/O 次数,是数据库和文件系统索引的底层基石。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/27 17:39:43

揭秘ECAPA-TDNN模型结构:MindSpore-Lab核心改进解析与完整指南

揭秘ECAPA-TDNN模型结构&#xff1a;MindSpore-Lab核心改进解析与完整指南 【免费下载链接】ecapatdnn 项目地址: https://ai.gitcode.com/hf_mirrors/MindSpore-Lab/ecapatdnn ECAPA-TDNN作为当前最先进的声纹识别模型&#xff0c;在MindSpore-Lab的优化实现下展现出了…

作者头像 李华
网站建设 2026/5/27 17:39:38

《Linux 环境变量:读懂其作用、分类及自定义配置技巧》

前引&#xff1a;提到 Linux 环境变量&#xff0c;很多新手都会觉得它高深莫测&#xff0c;仿佛是一道难以跨越的技术门槛。实际上&#xff0c;环境变量并非遥不可及&#xff0c;它就存在于我们日常的 Linux 操作中&#xff0c;比如执行echo $PATH查看路径&#xff0c;或是配置…

作者头像 李华
网站建设 2026/5/27 17:38:02

医疗物联网(IoHT)实战:安全、通信与硬件设计全解析

1. IoHT技术全景&#xff1a;从概念到落地的核心挑战医疗物联网&#xff08;IoHT&#xff09;早已不是实验室里的概念&#xff0c;而是正在深刻改变我们获取和管理健康方式的一场静默革命。作为一名在医疗科技领域摸爬滚打了十多年的从业者&#xff0c;我亲眼见证了它从简单的数…

作者头像 李华
网站建设 2026/5/27 17:36:05

【最优化】从等式到不等式:拉格朗日乘子法的完整演进与应用解析

1. 拉格朗日乘子法&#xff1a;从等式约束开始 第一次接触拉格朗日乘子法时&#xff0c;我正被一个简单的资源分配问题困扰&#xff1a;如何在固定预算下最大化产品收益。这就像在超市购物&#xff0c;既要买够生活必需品&#xff0c;又不能超出钱包里的钱。拉格朗日乘子法就是…

作者头像 李华
网站建设 2026/5/27 17:32:03

数字视频指纹识别技术深度解析:Vidupe的智能去重架构设计

数字视频指纹识别技术深度解析&#xff1a;Vidupe的智能去重架构设计 【免费下载链接】vidupe Vidupe is a program that can find duplicate and similar video files. V1.211 released on 2019-09-18, Windows exe here: 项目地址: https://gitcode.com/gh_mirrors/vi/vidu…

作者头像 李华
网站建设 2026/5/27 17:31:09

WebVOWL可视化工具:3步快速掌握本体数据可视化的完整路径

WebVOWL可视化工具&#xff1a;3步快速掌握本体数据可视化的完整路径 【免费下载链接】WebVOWL Visualizing ontologies on the Web 项目地址: https://gitcode.com/gh_mirrors/we/WebVOWL 还在为复杂的RDF和OWL本体数据而头疼吗&#xff1f;WebVOWL为您提供了一种直观、…

作者头像 李华