|
| 1 | +\title{"深入理解并实现基本的 B 树数据结构"} |
| 2 | +\author{"杨其臻"} |
| 3 | +\date{"May 08, 2025"} |
| 4 | +\maketitle |
| 5 | +在传统二叉搜索树中,每个节点只能存储一个关键字并拥有最多两个子节点。这种结构在内存中表现良好,但面对磁盘存储时,频繁的随机 I/O 会导致性能急剧下降。B 树通过多路平衡的设计,将多个关键字和子节点集中在单个节点中,使得一次磁盘读取可以获取更多有效数据。这种特性使其成为数据库、文件系统等场景的核心数据结构。本文将解析 B 树的核心原理,并基于 Python 实现一个支持插入、删除和查找的 B 树结构。\par |
| 6 | +\chapter{B 树的基础理论} |
| 7 | +\section{B 树的定义与特性} |
| 8 | +B 树是一种多路平衡搜索树,其核心特征在于「平衡性」与「多路分支」。每个节点最多包含 $m-1$ 个关键字($m$ 为阶数),非根节点至少包含 $\lceil m/2 \rceil -1$ 个关键字。所有叶子节点位于同一层,确保从根节点到任意叶子节点的路径长度相同。例如,一个 3 阶 B 树中,根节点可能包含 1-2 个关键字,非根节点至少包含 1 个关键字。\par |
| 9 | +与二叉搜索树相比,B 树通过减少树的高度降低了磁盘访问次数。而相较于 B+ 树,B 树允许在内部节点存储数据,这使得某些场景下的查询效率更高,但牺牲了范围查询的性能。\par |
| 10 | +\section{关键操作逻辑} |
| 11 | +\textbf{查找操作}从根节点开始,逐层比较关键字以确定下一步搜索的子节点。例如,若当前节点关键字为 [10, 20],查找值 15 时,会选择第二个子节点(对应区间 10 < 15 ≤ 20)。\par |
| 12 | +\textbf{插入操作}需维护节点的关键字数量上限。当节点关键字数超过 $m-1$ 时,需进行分裂:将中间关键字提升至父节点,左右两部分形成两个新子节点。若分裂传递到根节点,则树的高度增加。\par |
| 13 | +\textbf{删除操作}更为复杂。若删除关键字后节点仍满足最小关键字数要求,则直接删除;否则需通过「借位」从兄弟节点获取关键字,或与兄弟节点「合并」以维持平衡。\par |
| 14 | +\chapter{B 树的实现细节} |
| 15 | +\section{节点结构与初始化} |
| 16 | +B 树的节点需包含关键字列表、子节点列表以及是否为叶子节点的标志。以下 Python 代码定义了节点类:\par |
| 17 | +\begin{lstlisting}[language=python] |
| 18 | +class BTreeNode: |
| 19 | + def __init__(self, leaf=False): |
| 20 | + self.keys = [] # 存储关键字的列表 |
| 21 | + self.children = [] # 存储子节点的列表 |
| 22 | + self.leaf = leaf # 是否为叶子节点 |
| 23 | +\end{lstlisting} |
| 24 | +初始化 B 树时需指定阶数 $m$,并创建空的根节点。例如,阶数为 3 的 B 树初始状态为一个空根节点。\par |
| 25 | +\section{插入操作的实现} |
| 26 | +插入操作的核心是递归查找插入位置,并在必要时分裂节点。以下代码展示了插入逻辑的关键片段:\par |
| 27 | +\begin{lstlisting}[language=python] |
| 28 | +def insert(self, key): |
| 29 | + root = self.root |
| 30 | + if len(root.keys) == (2 * self.m) - 1: # 根节点已满 |
| 31 | + new_root = BTreeNode(leaf=False) |
| 32 | + new_root.children.append(root) |
| 33 | + self._split_child(new_root, 0) # 分裂原根节点 |
| 34 | + self.root = new_root # 更新根节点 |
| 35 | + self._insert_non_full(self.root, key) |
| 36 | +\end{lstlisting} |
| 37 | +\verb!_split_child! 方法负责分裂子节点:\par |
| 38 | +\begin{lstlisting}[language=python] |
| 39 | +def _split_child(self, parent, index): |
| 40 | + child = parent.children[index] |
| 41 | + new_node = BTreeNode(leaf=child.leaf) |
| 42 | + mid = len(child.keys) // 2 |
| 43 | + parent.keys.insert(index, child.keys[mid]) # 中间关键字提升至父节点 |
| 44 | + new_node.keys = child.keys[mid+1:] # 右半部分成为新节点 |
| 45 | + child.keys = child.keys[:mid] # 左半部分保留 |
| 46 | + if not child.leaf: |
| 47 | + new_node.children = child.children[mid+1:] |
| 48 | + child.children = child.children[:mid+1] |
| 49 | + parent.children.insert(index+1, new_node) # 插入新子节点 |
| 50 | +\end{lstlisting} |
| 51 | +此代码中,\verb!mid! 变量确定分裂位置,原节点的右半部分被分离为独立节点,中间关键字提升至父节点。\par |
| 52 | +\chapter{代码实现与测试} |
| 53 | +\section{查找方法的实现} |
| 54 | +查找操作通过递归遍历树结构实现:\par |
| 55 | +\begin{lstlisting}[language=python] |
| 56 | +def search(self, key, node=None): |
| 57 | + if node is None: |
| 58 | + node = self.root |
| 59 | + i = 0 |
| 60 | + while i < len(node.keys) and key > node.keys[i]: |
| 61 | + i += 1 |
| 62 | + if i < len(node.keys) and key == node.keys[i]: |
| 63 | + return True # 找到关键字 |
| 64 | + elif node.leaf: |
| 65 | + return False # 到达叶子节点未找到 |
| 66 | + else: |
| 67 | + return self.search(key, node.children[i]) |
| 68 | +\end{lstlisting} |
| 69 | +时间复杂度为 $O(\log n)$,其中 $n$ 为关键字总数。\par |
| 70 | +\section{删除操作的边界测试} |
| 71 | +删除根节点是特殊场景。例如,当根节点无关键字且只有一个子节点时,需将子节点设为新根:\par |
| 72 | +\begin{lstlisting}[language=python] |
| 73 | +def delete(self, key): |
| 74 | + self._delete(self.root, key) |
| 75 | + if len(self.root.keys) == 0 and not self.root.leaf: |
| 76 | + self.root = self.root.children[0] # 降低树高度 |
| 77 | +\end{lstlisting} |
| 78 | +测试时需验证删除后所有节点仍满足 B 树的平衡条件,例如通过遍历检查每个节点的关键字数量是否在允许范围内。\par |
| 79 | +\chapter{B 树的应用与优化} |
| 80 | +\section{实际应用场景} |
| 81 | +在 MySQL 的 InnoDB 引擎中,B+ 树作为索引结构,其叶子节点通过链表连接以支持高效范围查询。而原始 B 树因内部节点可存储数据,适用于需要频繁随机访问的场景,如某些文件系统的元数据管理。\par |
| 82 | +\section{优化方向} |
| 83 | +B+ 树通过将数据仅存储在叶子节点,减少了内部节点的大小,从而在相同磁盘页中容纳更多关键字。此外,Blink-Tree 通过添加「右兄弟指针」支持并发访问,允许在修改节点时其他线程继续读取旧版本数据。\par |
| 84 | +B 树通过巧妙的多路平衡设计,在磁盘存储场景中展现出卓越性能。尽管其实现复杂度较高,但理解其核心原理并动手实现,是掌握高级数据结构的必经之路。读者可进一步探索 B+ 树或并发 B 树变种,以应对更复杂的工程需求。\par |
0 commit comments