二叉树(前序,中序,后序,层序)遍历递归与循环的python实现

二叉树的遍历是在面试使比较常见的项目了。对于二叉树的前中后层序遍历,每种遍历都可以递归和循环两种实现方法,且每种遍历的递归实现都比循环实现要简洁。下面做一个小结。
一、中序遍历
前中后序三种遍历方法对于左右结点的遍历顺序都是一样的(先左后右),唯一不同的就是根节点的出现位置。对于中序遍历来说,根结点的遍历位置在中间。
所以中序遍历的顺序:左中右
1.1 递归实现
每次递归,只需要判断结点是不是None,否则按照左中右的顺序打印出结点value值。
class Solution:
def inorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
if not root:
return []
return self.inorderTraversal(root.left) + [root.val] + self.inorderTraversal(root.right)
1.2 循环实现
循环比递归要复杂得多,因为你得在一个函数中遍历到所有结点。但是有句话很重要:
对于树的遍历,循环操作基本上要用到栈(stack)这个结构
对于中序遍历的循环实现,每次将当前结点(curr)的左子结点push到栈中,直到当前结点(curr)为None。这时,pop出栈顶的第一个元素,设其为当前结点,并输出该结点的value值,且开始遍历该结点的右子树。

例如,对于上图的一个二叉树,其循环遍历过程如下表:
| No. | 输出列表sol | 栈stack | 当前结点curr |
|---|---|---|---|
| 1 | [] | [] | 1 |
| 2 | [] | [1] | 2 |
| 3 | [] | [1,2] | 4 |
| 4 | [] | [1,2,4] | None |
| 5 | [4] | [1,2] | 4 -> None(4的右结点) |
| 6 | [4,2] | [1] | 2 -> 5 |
| 7 | [4,2] | [1,5] | None(5的左结点) |
| 8 | [4,2,5] | [1] | 5 -> None(5的右结点) |
| 9 | [4,2,5,1] | [] | 3 |
| 10 | [4,2,5,1] | [3] | None |
| 11 | [4,2,5,1,3] | [] | None |
可见,规律为:当前结点curr不为None时,每一次循环将当前结点curr入栈;当前结点curr为None时,则出栈一个结点,且打印出栈结点的value值。整个循环在stack和curr皆为None的时候结束。
class Solution:
def inorderTraversal(self, root):
stack = []
sol = []
curr = root
while stack or curr:
if curr:
stack.append(curr)
curr = curr.left
else:
curr = stack.pop()
sol.append(curr.val)
curr = curr.right
return sol
二、前序遍历和后序遍历
按照上面的说法,前序遍历指根结点在最前面输出,所以前序遍历的顺序是:中左右
后序遍历指根结点在最后面输出,所以后序遍历的顺序是:左右中
2.1 递归实现
递归实现与中序遍历几乎完全一样,改变一下打印的顺序即可:
class Solution:
def preorderTraversal(self, root): ##前序遍历
"""
:type root: TreeNode
:rtype: List[int]
"""
if not root:
return []
return [root.val] + self.inorderTraversal(root.left) + self.inorderTraversal(root.right)
def postorderTraversal(self, root): ##后序遍历
"""
:type root: TreeNode
:rtype: List[int]
"""
if not root:
return []
return self.inorderTraversal(root.left) + self.inorderTraversal(root.right) + [root.val]
改动的地方只有return时函数的打印顺序。
2.2 循环实现
为什么把前序遍历和后序遍历放在一起呢?Leetcode上前序遍历是medium难度,后序遍历可是hard难度呢!
实际上,后序遍历不就是前序遍历的“反过程”嘛!
先看前序遍历。我们仍然使用栈stack,由于前序遍历的顺序是中左右,所以我们每次先打印当前结点curr,并将右子结点push到栈中,然后将左子结点设为当前结点。入栈和出栈条件(当前结点curr不为None时,每一次循环将当前结点curr入栈;当前结点curr为None时,则出栈一个结点)以及循环结束条件(整个循环在stack和curr皆为None的时候结束)与中序遍历一模一样。
再看后序遍历。由于后序遍历的顺序是左右中,我们把它反过来,则遍历顺序变成中左右,是不是跟前序遍历只有左右结点的差异了呢?然而左右的差异仅仅就是.left和.right的差异,在代码上只有机械的差别。
我们来看代码:
class Solution:
def preorderTraversal(self, root): ## 前序遍历
stack = []
sol = []
curr = root
while stack or curr:
if curr:
sol.append(curr.val)
stack.append(curr.right)
curr = curr.left
else:
curr = stack.pop()
return sol
def postorderTraversal(self, root): ## 后序遍历
stack = []
sol = []
curr = root
while stack or curr:
if curr:
sol.append(curr.val)
stack.append(curr.left)
curr = curr.right
else:
curr = stack.pop()
return sol[::-1]
代码的主体部分基本就是.right和.left交换了顺序,且后序遍历在最后输出的时候进行了反向(因为要从中右左变为左右中)
三、层序遍历
层序遍历也可以叫做宽度优先遍历:先访问树的第一层结点,再访问树的第二层结点...然后一直访问到最下面一层结点。在同一层结点中,以从左到右的顺序依次访问。
3.1 递归实现
递归函数需要有一个参数level,该参数表示当前结点的层数。遍历的结果返回到一个二维列表sol=[[]]中,sol中的每一个子列表保存了对应index层的从左到右的所有结点value值。
class Solution:
def levelOrder(self, root):
"""
:type root: TreeNode
:rtype: List[List[int]]
"""
def helper(node, level):
if not node:
return
else:
sol[level-1].append(node.val)
if len(sol) == level: # 遍历到新层时,只有最左边的结点使得等式成立
sol.append([])
helper(node.left, level+1)
helper(node.right, level+1)
sol = [[]]
helper(root, 1)
return sol[:-1]
PS:
Q:如果仍然按层遍历,但是每层从右往左遍历怎么办呢?
A:将上面的代码left和right互换即可
Q:如果仍然按层遍历,但是我要第一层从左往右,第二层从右往左,第三从左往右...这种zigzag遍历方式如何实现?
A:将sol[level-1].append(node.val)进行一个层数奇偶的判断,一个用append(),一个用insert(0,)
if level%2==1:
sol[level-1].append(node.val)
else:
sol[level-1].insert(0, node.val)
3.2 循环实现
这里的循环实现不能用栈了,得用队列queue。因为每一层都需要从左往右打印,而每打印一个结点都会在队列中依次添加其左右两个子结点,每一层的顺序都是一样的,故必须采用先进先出的数据结构。
以下代码的打印结果为一个一维列表,没有采用二维列表的形式。
class Solution:
def levelOrder(self, root):
if not root:
return []
sol = []
curr = root
queue = [curr]
while queue:
curr = queue.pop(0)
sol.append(curr.val)
if curr.left:
queue.append(curr.left)
if curr.right:
queue.append(curr.right)
return sol
其实,如果需要打印成zigzag形式(相邻层打印顺序相反),则可以采用栈stack数据结构,正好符合先进后出的形式。不过在代码上还要进行其他改动。
二叉树(前序,中序,后序,层序)遍历递归与循环的python实现的更多相关文章
- 算法进阶面试题03——构造数组的MaxTree、最大子矩阵的大小、2017京东环形烽火台问题、介绍Morris遍历并实现前序/中序/后序
接着第二课的内容和带点第三课的内容. (回顾)准备一个栈,从大到小排列,具体参考上一课.... 构造数组的MaxTree [题目] 定义二叉树如下: public class Node{ public ...
- 前、中、后序遍历随意两种是否能确定一个二叉树?理由? && 栈和队列的特点和区别
前序和后序不能确定二叉树理由:前序和后序在本质上都是将父节点与子结点进行分离,但并没有指明左子树和右子树的能力,因此得到这两个序列只能明确父子关系,而不能确定一个二叉树. 由二叉树的中序和前序遍历序列 ...
- 二叉树 遍历 先序 中序 后序 深度 广度 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- SDUT 1489 求二叉树的先序遍历 (中序后序还原二叉树)
求二叉树的先序遍历 Time Limit: 1000MS Memory Limit: 65536KB Submit Statistic Discuss Problem Description 已知一 ...
- URAL 1136 Parliament 二叉树水题 BST后序遍历建树
二叉树水题,特别是昨天刚做完二叉树用中序后序建树,现在来做这个很快的. 跟昨天那题差不多,BST后序遍历的特型,找到最后那个数就是根,向前找,比它小的那块就是他的左儿子,比它大的那块就是右儿子,然后递 ...
- SDUT OJ 数据结构实验之二叉树八:(中序后序)求二叉树的深度
数据结构实验之二叉树八:(中序后序)求二叉树的深度 Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Discuss Probl ...
- SDUT-2804_数据结构实验之二叉树八:(中序后序)求二叉树的深度
数据结构实验之二叉树八:(中序后序)求二叉树的深度 Time Limit: 1000 ms Memory Limit: 65536 KiB Problem Description 已知一颗二叉树的中序 ...
- 前序+中序->后序 中序+后序->前序
前序+中序->后序 #include <bits/stdc++.h> using namespace std; struct node { char elem; node* l; n ...
- 给出 中序&后序 序列 建树;给出 先序&中序 序列 建树
已知 中序&后序 建立二叉树: SDUT 1489 Description 已知一棵二叉树的中序遍历和后序遍历,求二叉树的先序遍历 Input 输入数据有多组,第一行是一个整数t (t& ...
随机推荐
- Scrum立会报告+燃尽图(Final阶段第五次)
此作业要求参见:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2484 项目地址:https://coding.net/u/wuyy694 ...
- 实验三:敏捷开发与XP实践
Java实验三报告 一. 实验内容 (一)敏捷开发与XP 内容:1.敏捷开发(Agile Development)是一种以人为核心.迭代.循序渐进的开发方法. 2.极限编程(eXtreme Pro ...
- java程序设计课程实验报告1
北京电子科技学院(BESTI) 实 验 报 告 课程:Java程序设计 班级:1353 姓名:陈都 学号:20135328 成绩: 指导 ...
- ListViewAnimations使用时报错NoClassDefFoundError: com.nineoldandroids.animation.Animator
见 https://github.com/nhaarman/ListViewAnimations/issues/294 解决: Add this to your dependencies in you ...
- struts2返回List json
利用struts2-json-plugin 之前一直输出null.... 按网上的配也不行 后来不知道怎么突然可以了 赶紧记录一下 private List<Shop> moneyshop ...
- java中static使用之静态方法注意点
1.静态方法可以直接调用同类中的静态成员,但是不能直接调用非静态成员,这是为什么呢?大家想一下,静态成员在对象创建之前就要写入内存,所以它在内存中是实实在在的存在的,而非静态还不存在内存中,所以不能调 ...
- 3、第一个Python程序
现在,了解了如何启动和退出Python的交互式环境,我们就可以正式开始编写Python代码了. 在写代码之前,请千万不要用“复制”-“粘贴”把代码从页面粘贴到你自己的电脑上.写程序也讲究一个感觉,你需 ...
- xshell 常用快捷键
1.连接mysql数据库mysql -uroot -p -h127.0.0.1 -P3306 2.列出所有sessionshow full processlist; 3.查看20条执行时间最长的SQL ...
- php连接池 php–cp
原文地址:http://blog.sina.com.cn/s/blog_9eaa0f400102v9fd.html 数据库连接池php-cp介绍时间 2015-01-23 11:53:05 数据库连接 ...
- HHVM 3.0 发布,执行 PHP 的虚拟机
HHVM 详细介绍 HipHop VM(HHVM)是Facebook推出的用来执行PHP代码的虚拟机,它是一个PHP的JIT(Just-In- Time)编译器,同时具有产生快速代码和即时编译的优点. ...