数据结构之 二叉树:
什么是二叉树呢??
这个问题,是不可能回答的,这辈子都不可能的,请同学们自行google,
这篇博客,写的真的是图文并茂,推荐下
二叉查找树(一)之 图文解析 和 C语言的实现
不过我更推荐大家系统的学习一下《数据结构与算法》这门课。虽然枯燥而且貌似还用不上,谁知道呢。。一定是有用的,如果不想一直处于it链的底端,还是学习一下吧,算我求你们了。。。。
好!不扯了,真的推荐系统学习这门课程。特此推荐一本 《数据结构,xx考研xx》 具体不太清楚了,但是考研类的都大同小异吧,这本书比我本科课本好多了,没有那么多废话,说完概念就是练题。真的墙裂推荐!!!
好!现在就假设大家都有了二叉树的基本概念。
那么什么是二叉树呢?? 哈哈
建议:
在读文章的时候强烈建议大家找张纸和笔来,使用画图辅助理解!!!
数据结构本来就比较复杂,再加上逻辑步骤过多,我们的大脑只能记住7位步骤左右,所以还是那句话no picture say j8
另外:建议大家直接读源码吧,很多解释个人觉得越说越乱,不好意思!!
正文:
- 构造二叉树(构造完全二叉树)
- 改进版(构造任意二叉树)
- 广度优先(层次遍历)
- 深度优先(前,中,后,序遍历)
- 递归实现遍历算法
- 堆栈实现遍历算法(推荐 )
测试代码:
因为算法比较多所以先将测试代码奉上,请自行测试:
...
def main():
#创建一个二叉树对象
tree = B_Tree()
#以此向树中添加节点,i == 3的情况表示,一个空节点,看完后面就明白了
for i in range(10):
if i == 3:
i = None
tree.add(i)
#广度优先的层次遍历算法
print([i.data for i in tree.floor_travel()])
#前,中,后序 遍历算法(递归实现)
print([i.data for i in tree.front_travel()])
print([i.data for i in tree.middle_travel()])
print([i.data for i in tree.back_travel()])
print('------------------------------------')
#前,中,后序 遍历算法(堆栈实现)
print([i.data for i in tree.front_stank_travel()])
print([i.data for i in tree.middle_stank_travel()])
print([i.data for i in tree.back_stank_travel()])
if __name__ == "__main__":
main()
...
构造二叉树
#二叉树的节点
class Node(object):
def __init__(self,data=None,l_child=None, r_child=None):
self.data = data
self.l_child = l_child
self.r_child = r_child
class B_Tree(object):
def __init__(self, node=None):
self.root = node
def add(self, item=None):
node = Node(item)
#如果是空树,则直接添到根
if not self.root:
self.root = node
else:
#不为空,则按照 左右顺序 添加节点,这样构建出来的是一棵有序二叉树,且是完全二叉树
my_queue = []
my_queue.append(self.root)
while True:
cur_node = my_queue.pop(0)
if not cur_node.l_child:
cur_node.l_child = node
return
elif not cur_node.r_child:
cur_node.r_child = node
return
else:
my_queue.append(cur_node.l_child)
my_queue.append(cur_node.r_child)
按照层次遍历的顺序将节点入队列,然后判断该节点是否有左孩子,没有的话则直接将新节点挂到下面,否则是右边,建议同学们画图理解。这就是网上的大多数版本,这样构建出来的二叉树是一个完全二叉树
。那我们怎么才能构建一个更一般的二叉树呢。一个节点虽然是空节点但是我们不能用None来指代它
,我们必须需要一个Node对象来占位
所以我们使用下面的代码:
class B_Tree(object):
...
def add(self, item=None):
#如果输入item是None 则表示一个空节点
node = Node(data=item)
#如果是空树,则直接添到根
#此时判断空的条件为,
if not self.root or self.root.data is None:
self.root = node
else:
#不为空,则按照 左右顺序 添加节点
my_queue = []
my_queue.append(self.root)
while True:
cur_node = my_queue.pop(0)
#即如果该当前节点为空节点则直接跳过它,起到一个占位的作用
if cur_node.data is None:
continue
if not cur_node.l_child:
cur_node.l_child = node
return
elif not cur_node.r_child:
cur_node.r_child = node
return
else:
my_queue.append(cur_node.l_child)
my_queue.append(cur_node.r_child)
可以看到,我们主要是使用了一个 内容为None 的Node对象来占位
,代表该节点是个空节点,所以该节点不会有后代节点
。从而达到构造更一般的二叉树的目的,但是我们构建时同样按照完全二叉树的顺序来构建树,且必须用None
来占位表示空节点。和上面的代码主要有俩处不同:
-
if not self.root or self.root.data is None:
对于根节点来说,None
或者 内容为None
均表示空树,因为内容为None
不能有后代节点,对于根来说不合理,所以让它相当于None
,不表示占位作用 -
if cur_node.data is None:
此处已经是非根节点了,所以需要一个空节点来表示占位作用,当下次添加节点时遇到内容为None
的节点直接什么都不做,跳过它,此时会将节点添加到下一个节点上
广度优先(层次遍历)
其实我们在构建二叉树的时候就是层次遍历的方式,这样构建出来的树,就是符合完全二叉树的,遍历算法如下:
...
#层次遍历
def floor_travel(self):
#如果是空树则直接返回一个[]
if not self.root or self.root.data is None:
return []
else:
my_queue = []
#构造个容器来返回遍历的结果
re_queue = []
my_queue.append(self.root)
while my_queue:
cur_node = my_queue.pop(0)
re_queue.append(cur_node)
if cur_node.l_child:
my_queue.append(cur_node.l_child)
if cur_node.r_child:
my_queue.append(cur_node.r_child)
return re_queue
...
前,中,后序遍历 递归实现
class B_Tree(object):
...
#递归,前序遍历
def front_travel(self):
if not self.root or self.root.data is None:
return []
else:
re_queue = []
def loop(root):
if not root:
return
else:
#中 左 右
re_queue.append(root)
loop(root.l_child)
loop(root.r_child)
loop(self.root)
return re_queue
#递归,中序遍历
def middle_travel(self):
if not self.root or self.root.data is None:
return []
else:
re_queue = []
def loop(root):
if not root:
return
else:
#左 中 右
loop(root.l_child)
re_queue.append(root)
loop(root.r_child)
loop(self.root)
return re_queue
#递归,后序遍历
def back_travel(self):
if not self.root or self.root.data is None:
return []
else:
re_queue = []
def loop(root):
if not root:
return
else:
#左 右 中
loop(root.l_child)
loop(root.r_child)
re_queue.append(root)
loop(self.root)
return re_queue
...
如上,三种遍历方式,其实只有递归体内部的三条语句有点区别,用递归真的是很简单,但是递归会开辟大量的栈空间,会造成空间的浪费,更严重的是,如果递归深度太深将直接导致程序崩溃,所以我们推荐使用最后的使用堆栈的实现方式:
class B_Tree(object):
...
#堆栈 ,前序遍历:
def front_stank_travel(self):
if not self.root or self.root.data is None:
return []
else:
my_stank = []
my_queue = []
my_stank.append(self.root)
while my_stank:
cur_node = my_stank.pop()
my_queue.append(cur_node)
if cur_node.r_child:
my_stank.append(cur_node.r_child)
if cur_node.l_child:
my_stank.append(cur_node.l_child)
return my_queue
#堆栈 ,中序遍历:
def middle_stank_travel(self):
if not self.root or self.root.data is None:
return []
else:
my_stank = []
my_queue = []
tmp_list = []
my_stank.append(self.root)
while my_stank:
cur_node = my_stank.pop()
#my_queue.append(cur_node)
if cur_node.r_child and cur_node.r_child not in my_stank:
my_stank.append(cur_node.r_child)
if cur_node.l_child:
if cur_node not in tmp_list:
my_stank.append(cur_node)
tmp_list.append(cur_node)
else:
my_queue.append(cur_node)
tmp_list.remove(cur_node)
continue
my_stank.append(cur_node.l_child)
else:
my_queue.append(cur_node)
return my_queue
def back_stank_travel(self):
if not self.root or self.root.data is None:
return []
else:
my_stank = []
my_queue = []
tmp_list = []
my_stank.append(self.root)
while my_stank:
cur_node = my_stank[-1]
if cur_node.r_child and cur_node not in tmp_list:
my_stank.append(cur_node.r_child)
if cur_node.l_child and cur_node not in tmp_list:
my_stank.append(cur_node.l_child)
if cur_node in tmp_list or (cur_node.l_child is None and cur_node.r_child is None):
my_queue.append(my_stank.pop())
if cur_node in tmp_list:
tmp_list.remove(cur_node)
tmp_list.append(cur_node)
return my_queue
...
说实话这些方法,写的我很是不满意,感觉很复杂,但是是自己一中午的成果,所以还是写在这分享下。再次强烈建议大家读函数的时候,结合画图来了解。其实网上还有篇博文,它的方法比较简单点 ,但是思想差不多,建议大家学习下python实现二叉树和它的七种遍历
好了,大家有没有发现我们每次遍历都需要做判断树是否为空,这样做真的很麻烦,我们完全可以使用装饰器来把代码装饰下,不了解装饰器的同学可以看下我之前的Python 装饰器系列,绝壁比这篇写的好~~(虚)
好那我们上装饰器修改后的代码:
import functools
def is_empty(func):
@functools.wraps(func)
def wrapper(self):
if not self.root or self.root.data is None:
return []
else:
return func(self)
return wrapper
class B_Tree(object):
...
#递归,前序遍历
@is_empty
def front_travel(self):
re_queue = []
def loop(root):
if not root:
return
else:
#中 左 右
re_queue.append(root)
loop(root.l_child)
loop(root.r_child)
loop(self.root)
return re_queue
#堆栈 ,前序遍历:
@is_empty
def front_stank_travel(self):
my_stank = []
my_queue = []
my_stank.append(self.root)
while my_stank:
cur_node = my_stank.pop()
my_queue.append(cur_node)
if cur_node.r_child:
my_stank.append(cur_node.r_child)
if cur_node.l_child:
my_stank.append(cur_node.l_child)
return my_queue
...
使用测试代码同样可以完美输出。。。
总结:
构建任意的二叉树,而非完全二叉树
递归实现二叉树遍历,但是空间浪费严重
推荐使用堆栈的方式实现二叉树的遍历
使用装饰器优化代码
墙裂建议使用画图辅助!!!!
声明:
本人也是python 小白,加上数据结构本来就比较复杂,如果上述内容有讲的不对的地方还请各位批评指点。将不胜感激,再次感谢~~~
讲的不好,还请大家见谅~~~~