Skip to content

Commit 4847ccc

Browse files
committed
完成 BST 操作和时间复杂度
1 parent 813eb00 commit 4847ccc

9 files changed

+146
-5
lines changed

docs/17_二叉查找树/binary_search_tree.md

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
二叉树的一种应用就是来实现堆,今天我们再看看用二叉查找树。
44
前面有章节说到了查找操作,包括线性查找和二分查找,线性查找效率比较低,二分又要求必须是有序的序列,
5-
为了维持有序插入的代价比较高。能不能有一种插入和查找都比较快的数据结构呢?
5+
为了维持有序插入的代价比较高。能不能有一种插入和查找都比较快的数据结构呢?二叉查找树就是这样一种结构,可以高效地插入和查询节点。
66

77
# BST 定义
88

@@ -117,10 +117,19 @@ bst = BST.build_from(NODE_LIST)
117117

118118
## 插入
119119
插入节点的时候我们需要一直保持 BST 的性质,每次插入一个节点,我们都通过递归比较把它放到正确的位置。
120+
你会发现新节点总是被作为叶子结点插入。(请你思考这是为什么)
121+
122+
![](./bst_insert.png)
120123

121124
```py
122125
def _bst_insert(self, subtree, key, value):
123-
if subtree is None:
126+
""" 插入并且返回根节点
127+
128+
:param subtree:
129+
:param key:
130+
:param value:
131+
"""
132+
if subtree is None: # 插入的节点一定是根节点,包括 root 为空的情况
124133
subtree = BSTNode(key, value)
125134
elif key < subtree.key:
126135
subtree.left = self._bst_insert(subtree.left, key, value)
@@ -139,11 +148,95 @@ bst = BST.build_from(NODE_LIST)
139148
return True
140149
```
141150

142-
## 删除
151+
## 删除节点
152+
删除操作相比上边的操作要麻烦很多,首先需要定位一个节点,删除节点后,我们需要始终保持 BST 的性质。
153+
删除一个节点涉及到三种情况:
154+
155+
- 节点是叶节点
156+
- 节点有一个孩子
157+
- 节点有两个孩子
158+
159+
我们分别来看看三种情况下如何删除一个节点:
160+
161+
#### 删除叶节点
162+
这是最简单的一种情况,只需要把它的父亲指向它的指针设置为 None 就好。
163+
164+
![](./bst_remove_leaf.png)
165+
166+
#### 删除只有一个孩子的节点
167+
删除有一个孩子的节点时,我们拿掉需要删除的节点,之后把它的父亲指向它的孩子就行,以为根据 BST
168+
左子树都小于节点,右子树都大于节点的特性,删除它之后这个条件依旧满足。
169+
170+
![](./bst_remove_node_with_one_child.png)
171+
172+
#### 删除有两个孩子的内部节点
173+
假如我们想删除 12 这个节点改怎么做呢?你的第一反应可能是按照下图的方式:
174+
175+
![](./remove_interior_replace.png)
176+
177+
但是这种方式可能会影响树的高度,降低查找的效率。这里我们用另一种非常巧妙的方式。
178+
还记得上边提到的吗,如果你中序遍历 BST 并且输出每个节点的 key,你会发现就是一个有序的数组。
179+
[1 4 12 23 29 37 41 60 71 84 90 100]。 这里我们定义两个概念,逻辑前任(predecessor)和后继(successor),请看下图:
180+
181+
![](./predecessor_successor.png)
182+
183+
12 在中序遍历中的逻辑前任和后继分别是 4 和 23 节点。于是我们还有一种方法来删除 12 这个节点:
184+
185+
- 找到节点待删除节点 N(12) 的后继节点 S(23)
186+
- 复制节点 S 到节点 N
187+
- 删除节点 S
188+
189+
说白了就是找到后继并且替换,这里之所以能保证这种方法是正确的,你会发现替换后依旧是保持了 BST 的性质。
190+
有个问题是如何找到后继节点呢?待删除节点的右子树的最小的节点不就是后继嘛,上边我们已经实现了找到最小 key 的方法了。
191+
192+
193+
![](./find_successor.png)
194+
195+
我们开始编写代码实现,和之前的操作类似,我们还是通过辅助函数的形式来实现,这个递归函数会比较复杂,请你仔细理解:
196+
197+
```py
198+
def _bst_remove(self, subtree, key):
199+
"""删除节点并返回根节点"""
200+
if subtree is None:
201+
return None
202+
elif key < subtree.key:
203+
subtree.left = self._bst_remove(subtree.left, key)
204+
return subtree
205+
elif key > subtree.key:
206+
subtree.right = self._bst_remove(subtree.right, key)
207+
return subtree
208+
else: # 找到了需要删除的节点
209+
if subtree.left is None and subtree.right is None: # left node
210+
return None
211+
elif subtree.left is None or subtree.right is None: # 只有一个孩子
212+
if subtree.left is not None:
213+
return subtree.left
214+
else:
215+
return subtree.right
216+
else: # 俩孩子
217+
successor_node = self._bst_min_node(subtree.right)
218+
subtree.key, subtree.value = successor_node.key, subtree.value
219+
subtree.right = self._bst_remove(subtree.right, successor_node.key)
220+
return subtree
221+
222+
def remove(self, key):
223+
assert key in self
224+
self.size -= 1
225+
```
226+
227+
完整代码你可以在本章的 bst.py 找到。
228+
229+
# 时间复杂度分析
230+
231+
上边介绍的操作时间复杂度和二叉树的形状有关。平均来说时间复杂度是和树的高度成正比的,树的高度 h 是 log(n),
232+
但是最坏情况下以上操作的时间复杂度都是 O(n)。为了改善 BST 有很多变种,感兴趣请参考延伸阅读中的内容。
233+
234+
![](./bst_worstcase.png)
235+
143236

144237
# 练习题:
145238
- 请你实现查找 BST 最大值的函数
146239

147240

148241
# 延伸阅读
149-
- 《Data Structures and Algorithms in Python》14 章
242+
- 《Data Structures and Algorithms in Python》14 章,树的概念和算法还有很多,我们这里介绍最基本的

docs/17_二叉查找树/bst.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ def _bst_search(self, subtree, key):
3838
else:
3939
return subtree
4040

41+
def __contains__(self, key):
42+
"""实现 in 操作符"""
43+
return self._bst_search(self.root, key) is not None
44+
4145
def get(self, key, default=None):
4246
node = self._bst_search(self.root, key)
4347
if node is None:
@@ -58,7 +62,13 @@ def bst_min(self):
5862
return node.value if node else None
5963

6064
def _bst_insert(self, subtree, key, value):
61-
if subtree is None:
65+
""" 插入并且返回根节点
66+
67+
:param subtree:
68+
:param key:
69+
:param value:
70+
"""
71+
if subtree is None: # 插入的节点一定是根节点,包括 root 为空的情况
6272
subtree = BSTNode(key, value)
6373
elif key < subtree.key:
6474
subtree.left = self._bst_insert(subtree.left, key, value)
@@ -76,6 +86,35 @@ def add(self, key, value):
7686
self.size += 1
7787
return True
7888

89+
def _bst_remove(self, subtree, key):
90+
"""删除节点并返回根节点"""
91+
if subtree is None:
92+
return None
93+
elif key < subtree.key:
94+
subtree.left = self._bst_remove(subtree.left, key)
95+
return subtree
96+
elif key > subtree.key:
97+
subtree.right = self._bst_remove(subtree.right, key)
98+
return subtree
99+
else: # 找到了需要删除的节点
100+
if subtree.left is None and subtree.right is None: # left node
101+
return None
102+
elif subtree.left is None or subtree.right is None: # 只有一个孩子
103+
if subtree.left is not None:
104+
return subtree.left
105+
else:
106+
return subtree.right
107+
else: # 俩孩子
108+
successor_node = self._bst_min_node(subtree.right)
109+
subtree.key, subtree.value = successor_node.key, subtree.value
110+
subtree.right = self._bst_remove(subtree.right, successor_node.key)
111+
return subtree
112+
113+
def remove(self, key):
114+
assert key in self
115+
self.size -= 1
116+
return self._bst_remove(self.root, key)
117+
79118

80119
NODE_LIST = [
81120
{'key': 60, 'left': 12, 'right': 90, 'is_root': True},
@@ -105,3 +144,12 @@ def test_bst_tree():
105144

106145
bst.add(0, 0)
107146
assert bst.bst_min() == 0
147+
148+
bst.remove(12)
149+
assert bst.get(12) is None
150+
151+
bst.remove(1)
152+
assert bst.get(1) is None
153+
154+
bst.remove(29)
155+
assert bst.get(29) is None
65.2 KB
Loading
68.5 KB
Loading
Loading
41.3 KB
Loading
114 KB
Loading
Loading
Loading

0 commit comments

Comments
 (0)