Skip to content

Commit 27a8f58

Browse files
chore: automated publish
1 parent f9485e5 commit 27a8f58

3 files changed

Lines changed: 85 additions & 0 deletions

File tree

public/blog/2025-05-08/index.pdf

108 KB
Binary file not shown.

public/blog/2025-05-08/index.tex

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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

public/blog/2025-05-08/sha256

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
71e9a9b1c7d3aa65900818aa0702a3ce95c605e70b6910ea5e03b25e58537e8c

0 commit comments

Comments
 (0)