Skip to content

Commit 410cc53

Browse files
chore: automated publish
1 parent 68cfe5c commit 410cc53

File tree

3 files changed

+156
-0
lines changed

3 files changed

+156
-0
lines changed

public/blog/2025-07-22/index.pdf

131 KB
Binary file not shown.

public/blog/2025-07-22/index.tex

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
\title{"深入理解并实现二叉搜索树(Binary Search Tree)—— 从理论到代码实践"}
2+
\author{"杨子凡"}
3+
\date{"Jul 22, 2025"}
4+
\maketitle
5+
数据结构是构建高效算法的基石,其中二叉搜索树(Binary Search Tree,BST)因其简洁的有序性设计,在数据库索引、游戏对象管理等场景中广泛应用。本文将从核心概念出发,逐步实现完整 BST 结构,揭示其性能特性与实现陷阱。\par
6+
\chapter{二叉搜索树的核心概念}
7+
二叉搜索树本质是满足特定有序性质的二叉树结构。每个节点包含键值(Key)、左子节点(Left)、右子节点(Right)及可选的父节点(Parent)指针。其核心性质可表述为:对于任意节点,左子树所有节点值小于该节点值,右子树所有节点值大于该节点值,数学表达为:\par
8+
设节点 $x$,则 $\forall y \in left(x)$ 满足 $y.key < x.key$$\forall z \in right(x)$ 满足 $z.key > x.key$\par
9+
区别于普通二叉树的无序性,BST 的有序性使其查找复杂度从 $O(n)$ 优化至 $O(h)$$h$ 为树高)。需明确高度(Height)与深度(Depth)的区别:深度指从根节点到当前节点的路径长度,高度指从当前节点到最深叶子节点的路径长度。叶子节点(无子节点)与内部节点(至少有一个子节点)共同构成树形结构。\par
10+
\chapter{BST 的四大核心操作}
11+
\section{查找操作}
12+
查找操作充分利用 BST 的有序性进行剪枝。递归实现通过比较目标键值与当前节点值决定搜索方向:\par
13+
\begin{lstlisting}[language=python]
14+
def search(root, key):
15+
# 基线条件:空树或找到目标
16+
if not root or root.val == key:
17+
return root
18+
# 目标值小于当前节点值则搜索左子树
19+
if key < root.val:
20+
return search(root.left, key)
21+
# 否则搜索右子树
22+
return search(root.right, key)
23+
\end{lstlisting}
24+
时间复杂度在平衡树中为 $O(\log n)$,最坏情况(退化成链表)为 $O(n)$。迭代版本通过循环替代递归栈,减少空间开销。\par
25+
\section{插入操作}
26+
插入需维护 BST 的有序性。迭代实现通过追踪父节点指针确定插入位置:\par
27+
\begin{lstlisting}[language=python]
28+
def insert(root, key):
29+
node = Node(key) # 创建新节点
30+
if not root:
31+
return node # 空树直接返回新节点
32+
33+
curr, parent = root, None
34+
# 循环找到合适的叶子位置
35+
while curr:
36+
parent = curr
37+
curr = curr.left if key < curr.val else curr.right
38+
39+
# 根据键值大小决定插入方向
40+
if key < parent.val:
41+
parent.left = node
42+
else:
43+
parent.right = node
44+
return root
45+
\end{lstlisting}
46+
重复键处理通常采用拒绝插入或插入到右子树(视为大于等于)。递归实现代码更简洁但存在栈溢出风险。\par
47+
\section{删除操作}
48+
删除是 BST 最复杂的操作,需处理三种情况:\par
49+
\begin{itemize}
50+
\item \textbf{叶子节点}:直接解除父节点引用
51+
\item \textbf{单子节点}:用子节点替换被删节点
52+
\item \textbf{双子节点}:用后继节点(右子树最小节点)替换被删节点值,再递归删除后继节点
53+
\end{itemize}
54+
\begin{lstlisting}[language=python]
55+
def delete(root, key):
56+
if not root:
57+
return None
58+
59+
# 递归查找目标节点
60+
if key < root.val:
61+
root.left = delete(root.left, key)
62+
elif key > root.val:
63+
root.right = delete(root.right, key)
64+
else:
65+
# 情况 1:单子节点或叶子节点
66+
if not root.left:
67+
return root.right
68+
if not root.right:
69+
return root.left
70+
71+
# 情况 2:双子节点处理
72+
succ = find_min(root.right) # 查找后继
73+
root.val = succ.val # 值替换
74+
root.right = delete(root.right, succ.val) # 删除原后继
75+
76+
return root
77+
78+
def find_min(node):
79+
while node.left:
80+
node = node.left
81+
return node
82+
\end{lstlisting}
83+
关键点在于处理双子节点时,后继节点替换后需递归删除原后继节点。未正确处理父指针更新是常见错误。\par
84+
\section{遍历操作}
85+
中序遍历(左-根-右)是 BST 的核心遍历方式,能按升序输出所有节点:\par
86+
\begin{lstlisting}[language=python]
87+
def in_order(root):
88+
if root:
89+
in_order(root.left)
90+
print(root.val)
91+
in_order(root.right)
92+
\end{lstlisting}
93+
前序遍历(根-左-右)用于复制树结构,后序遍历(左-右-根)用于安全删除。层序遍历需借助队列实现广度优先搜索。\par
94+
\chapter{复杂度分析与性能陷阱}
95+
BST 的性能高度依赖于树的平衡性。理想情况下,随机数据构建的 BST 高度接近 $\log_2 n$,此时查找、插入、删除操作时间复杂度均为 $O(\log n)$。最坏情况(如输入有序序列)会退化成链表,高度 $h = n$,操作复杂度恶化至 $O(n)$\par
96+
空间复杂度稳定为 $O(n)$,主要用于存储节点信息。需警惕有序插入导致的退化问题,这是引入 AVL 树、红黑树等自平衡结构的根本原因——通过旋转操作动态维持树高在 $O(\log n)$ 级别。\par
97+
\chapter{手把手实现完整 BST 类}
98+
以下 Python 实现包含核心方法:\par
99+
\begin{lstlisting}[language=python]
100+
class Node:
101+
__slots__ = ('val', 'left', 'right') # 优化内存
102+
def __init__(self, val):
103+
self.val = val
104+
self.left = None
105+
self.right = None
106+
107+
class BST:
108+
def __init__(self):
109+
self.root = None
110+
111+
def insert(self, val):
112+
# 封装插入操作(见前文迭代实现)
113+
114+
def delete(self, val):
115+
# 封装删除操作(见前文递归实现)
116+
117+
def search(self, val):
118+
curr = self.root
119+
while curr:
120+
if curr.val == val:
121+
return True
122+
curr = curr.left if val < curr.val else curr.right
123+
return False
124+
125+
def in_order(self):
126+
# 返回中序遍历生成器
127+
def _traverse(node):
128+
if node:
129+
yield from _traverse(node.left)
130+
yield node.val
131+
yield from _traverse(node.right)
132+
return list(_traverse(self.root))
133+
134+
def find_min(self):
135+
# 辅助函数:找最小值节点
136+
if not self.root:
137+
return None
138+
node = self.root
139+
while node.left:
140+
node = node.left
141+
return node.val
142+
\end{lstlisting}
143+
单元测试应覆盖:空树操作、删除根节点、连续插入重复值、有序序列插入等边界场景。例如删除根节点时需验证新根的正确性。\par
144+
\chapter{进阶讨论}
145+
BST 的局限性主要源于不平衡风险。解决方案包括:\par
146+
\begin{itemize}
147+
\item \textbf{AVL 树}:通过平衡因子(左右子树高度差)触发旋转
148+
\item \textbf{红黑树}:放宽平衡条件,通过节点着色和旋转维护平衡
149+
\item \textbf{Treap}:结合 BST 与堆的特性,以随机优先级维持平衡
150+
\end{itemize}
151+
实际应用中,C++ STL 的 \texttt{std::map} 采用红黑树实现,文件系统目录树也常使用 BST 变体。这些结构在 $O(\log n)$ 时间内保证操作效率,代价是实现复杂度显著提升。\par
152+
二叉搜索树以简洁的结构实现了高效的有序数据管理,其 $O(\log n)$ 的平均性能与 $O(n)$ 的最坏情况揭示了数据结构设计的平衡艺术。掌握 BST 为理解更复杂的平衡树奠定基础,在工程实践中需根据场景需求权衡实现复杂度与性能稳定性。\par
153+
\begin{quote}
154+
\textbf{附录}:完整代码实现与可视化工具详见 \href{https://github.com/example/bst-impl}{GitHub 仓库}。推荐阅读《算法导论》第 12 章深入探究树结构数学证明。\par
155+
\end{quote}

public/blog/2025-07-22/sha256

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

0 commit comments

Comments
 (0)