Skip to content

Commit 3d40bf2

Browse files
committed
python实现二叉树查找算法
1 parent 68f1af0 commit 3d40bf2

File tree

9 files changed

+460
-0
lines changed

9 files changed

+460
-0
lines changed

binary_tree.md

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
#问题
2+
3+
二叉树查找
4+
5+
#思路说明
6+
7+
二叉树查找是一个面对动态数据比较常用的查找算法。本文根据下面地址文章翻译,并根据本人的理解进行适当修改。
8+
9+
原文地址:http://www.laurentluce.com/posts/binary-search-tree-library-in-python/comment-page-1/
10+
11+
###二叉树查找的定义
12+
13+
定义内容可以参阅Wikipedia:http://en.wikipedia.org/wiki/Binary_tree
14+
15+
这里是中文的:http://zh.wikipedia.org/wiki/%E4%BA%8C%E5%85%83%E6%90%9C%E5%B0%8B%E6%A8%B9
16+
17+
摘要其中对二叉树的描述:
18+
19+
>>二叉树查找的性质:
20+
21+
>>1. 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
22+
>>2. 任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
23+
>>3. 任意节点的左、右子树也分别为二叉查找树。
24+
>>4. 没有键值相等的节点(no duplicate nodes)。
25+
26+
>>二叉查找树相比于其他数据结构的优势在于查找、插入的时间复杂度较低。为O(log n)。二叉查找树是基础性数据结构,用于构建更为抽象的数据结构,如集合、multiset、关联数组等。
27+
28+
>>虽然二叉查找树的最坏效率是O(n),但它支持动态查询,且有很多改进版的二叉查找树可以使树高为O(logn),如SBT,AVL,红黑树等.
29+
30+
###用python实现二叉树查找
31+
32+
以下面图示的二叉树为例说明查找算法
33+
34+
![](./pic/binarytree1.png)
35+
36+
**Node 类**
37+
38+
创建一个类,命名为Node,做为二叉树节点结构,其中包括:左枝、右枝、节点数据三个变量。
39+
40+
class Node:
41+
"""
42+
二叉树左右枝
43+
"""
44+
def __init__(self, data):
45+
"""
46+
节点结构
47+
48+
"""
49+
self.left = None
50+
self.right = None
51+
self.data = data
52+
53+
例如创建一个含整数8的节点。因为仅仅创建一个节点,所以左右枝都是None。
54+
55+
root = Node(8)
56+
57+
这样就得到如下图所示的只有一个节点的树。
58+
59+
![](./pics/binarytree2.png)
60+
61+
**插入方法**
62+
63+
现在已经有了一棵光秃秃的树,要有枝杈和叶子,就必须用插入数据方法,添加新的节点和数据。
64+
65+
def insert(self, data):
66+
"""
67+
插入节点数据
68+
"""
69+
if data < self.data:
70+
if self.left is None:
71+
self.left = Node(data)
72+
else:
73+
self.left.insert(data)
74+
elif data > self.data:
75+
if self.right is None:
76+
self.right = Node(data)
77+
else:
78+
self.right.insert(data)
79+
80+
承接前面的操作,可以用下面的方式增加树的枝杈和叶子(左右枝以及节点数据)。
81+
82+
root.insert(3)
83+
root.insert(10)
84+
root.insert(1)
85+
86+
当增加了第二个节点数据3,程序会:
87+
88+
- 第一步,root会调用insert(),其参数是data=3
89+
- 第二步,比较3和8(已有的根节点),3比8小。并且树的左枝还是None,于是就在左边建立一个新的节点。
90+
91+
增加第三个节点数据10,程序会:
92+
93+
- 第一步,跟前面的第一步一样,只不过data=10
94+
- 第二步,发现10大于8,同时右边是None,于是就把它做为右边新建分支的节点数据。
95+
96+
增加第四个节点数据1,程序会:
97+
98+
- 第一步,同前,data=1
99+
- 第二步,1小于8,所以要放在树的左枝;
100+
- 第三步,左枝已经有子节点3,该节点再次调用insert()方法,1小于3,所以1就做为3的子节点,且放在原本就是None的左侧。
101+
102+
如此,就形成了下图的树
103+
104+
![](./pics/binarytree3.png)
105+
106+
继续增加节点数据
107+
108+
root.insert(6)
109+
root.insert(4)
110+
root.insert(7)
111+
root.insert(14)
112+
root.insert(13)
113+
114+
最终形成下图的树
115+
116+
![](./pic/binarytree4.png)
117+
118+
**遍历树**
119+
120+
此方法用于查找树中的某个节点,如果找到了,就返回该节点,否则返回None。为了方便,也返回父节点。
121+
122+
def lookup(self, data, parent=None):
123+
"""
124+
遍历二叉树
125+
"""
126+
if data < self.data:
127+
if self.left is None:
128+
return None, None
129+
return self.left.lookup(data, self)
130+
elif data > self.data:
131+
if self.right is None:
132+
return None, None
133+
return self.right.lookup(data, self)
134+
else:
135+
return self, parent
136+
137+
测试一下,找一找数据为6的节点
138+
139+
node, parent = root.lookup(6)
140+
141+
调用lookup()后,程序会这么干:
142+
143+
1. 调用lookup(),传递参数data=6,默认parent=None
144+
2. data=6,小于根节点的值8
145+
3. 指针转到根节点左侧,此时:data=6,parent=8,再次调用lookup()
146+
4. data=6大于左侧第一层节点数据3
147+
5. 指针转到3的右侧分支,data=6,parent=3,再次调用lookup()
148+
6. 节点数据等于6,于是返回这个节点和它的父节点3
149+
150+
**删除方法**
151+
152+
删除节点数据。代码如下:
153+
154+
def delete(self, data):
155+
"""
156+
删除节点
157+
"""
158+
node, parent = self.lookup(data) #已有节点
159+
if node is not None:
160+
children_count = node.children_count() #判断子节点数
161+
if children_count == 0:
162+
# 如果该节点下没有子节点,即可删除
163+
if parent.left is node:
164+
parent.left = None
165+
else:
166+
parent.right = None
167+
del node
168+
elif children_count == 1:
169+
# 如果有一个子节点,则让子节点上移替换该节点(该节点消失)
170+
if node.left:
171+
n = node.left
172+
else:
173+
n = node.right
174+
if parent:
175+
if parent.left is node:
176+
parent.left = n
177+
else:
178+
parent.right = n
179+
del node
180+
else:
181+
# 如果有两个子节点,则要判断节点下所有叶子
182+
parent = node
183+
successor = node.right
184+
while successor.left:
185+
parent = successor
186+
successor = successor.left
187+
node.data = successor.data
188+
if parent.left == successor:
189+
parent.left = successor.right
190+
else:
191+
parent.right = successor.right
192+
193+
在上述方法中,得到当前节点下的子节点数目后,需要进行三种情况的判断
194+
195+
- 如果没有子节点,直接删除
196+
- 如果有一个子节点,要将下一个子节点上移到当前节点,即替换之
197+
- 如果有两个子节点,要对自己点的数据进行判断,并从新安排节点排序
198+
199+
上述方法中用到了统计子节点数目的方法,代码如下:
200+
201+
def children_count(self):
202+
"""
203+
子节点个数
204+
"""
205+
cnt = 0
206+
if self.left:
207+
cnt += 1
208+
if self.right:
209+
cnt += 1
210+
return cnt
211+
212+
例1:删除数据为1的节点,它是3的子节点,1后面没有子节点
213+
214+
root.delete(1)
215+
216+
![](./pics/binarytree5.png)
217+
218+
例2:删除数据为14的节点,它是10的子节点,它下面有唯一一个子节点13,13替换之。
219+
220+
root.delete(14)
221+
222+
![](.pics/binarytree6.png)
223+
224+
例3:来个复杂的,删除节点数据为3的节点,它下面有两个节点,而节点6下面又有两个4,7。需要一个临时变量successor,将节点3下面的子节点进行查询,并把小于3下面的第一级子节点6左测节点数据4(该数据一定小于其父节点6)替换当前节点3,维持二叉树结构。如下图:
225+
226+
root.delete(3)
227+
228+
![](.pics/binarytree7.png)
229+
230+
**比较两个二叉树**
231+
232+
比较两个二叉树的方法中,只要有一个节点(叶子)与另外一个树的不同,就返回False,也包括缺少对应叶子的情况。
233+
234+
def compare_trees(self, node):
235+
"""
236+
比较两棵树
237+
"""
238+
if node is None:
239+
return False
240+
if self.data != node.data:
241+
return False
242+
res = True
243+
if self.left is None:
244+
if node.left:
245+
return False
246+
else:
247+
res = self.left.compare_trees(node.left)
248+
if res is False:
249+
return False
250+
if self.right is None:
251+
if node.right:
252+
return False
253+
else:
254+
res = self.right.compare_trees(node.right)
255+
return res
256+
257+
例如,比较tree(3,8,10)和tree(3,8,11)
258+
259+
#root2 是tree(3,8,11)的根
260+
#root 是tree(3,8,10)的根
261+
root.compare_trees(root2)
262+
263+
执行上面的代码,程序会这么走:
264+
265+
1. root调用compare_trees()方法
266+
2. root有左侧子节点,调用该节点的compare_trees()
267+
3. 两个左侧子节点比较,返回true
268+
4. 按照前面的过程,比较右侧节点,发现不同,则返回False
269+
270+
**打印树**
271+
272+
把二叉树按照一定的顺序打印出来。不需要参数了。做法就是先左后右(左小于右)。
273+
274+
def print_tree(self):
275+
"""
276+
按顺序打印数的内容
277+
"""
278+
if self.left:
279+
self.left.print_tree()
280+
print self.data,
281+
if self.right:
282+
self.right.print_tree()
283+
284+
操作一下:
285+
286+
root.print_tree()
287+
288+
输出: 1, 3, 4, 6, 7, 8, 10, 13, 14
289+
290+
**包含所有树元素的生成器**
291+
292+
创建一个包含所有树元素的生成器,有时候是有必要的。考虑到内存问题,没有必要实时生成所有节点数据列表,而是要每次调用此方法时,它返回的下一个节点的值。为此,使用它返回一个对象,并停止在那里,那么该函数将在下一次调用方法时从那里继续通过yield关键字返回值。在这种情况下,要使用堆栈,不能使用递归。
293+
294+
def tree_data(self):
295+
"""
296+
二叉树数据结构
297+
"""
298+
stack = []
299+
node = self
300+
while stack or node:
301+
if node:
302+
stack.append(node)
303+
node = node.left
304+
else:
305+
node = stack.pop()
306+
yield node.data
307+
node = node.right
308+
309+
举例,通过循环得到树:
310+
311+
for data in root.tree_data():
312+
print data
313+
314+
![](./pics/binarytree1.png)
315+
316+
程序会按照先左后右边的原子将数据入栈、出栈,顺序取出值,并返回结果

0 commit comments

Comments
 (0)