【算法】二叉树遍历算法总结:前序中序后序遍历

article/2025/10/8 17:53:47

前言

二叉树遍历是非常经典的算法题,也是二叉树的一道基础算法题。

但是在平常的笔试面试中,其出现的频率其实并不是特别的高,我推测是这种题目相对来说比较基础,算是一个基础知识点。

比如剑指offer中出现的后序遍历题目,是给出一个数字序列,让你判断是不是平衡二叉树后序遍历序列,这样出题的难度比直接让你写后序遍历难很多。

但是,二叉树遍历容易吗?在递归方法下,前中后序遍历都是一个思路,理解起来也比较容易。

但是只是用迭代的话,二叉树遍历其实是有难度的!,这也是为什么LeetCode会在这三题题目的下方写出进阶: 递归算法很简单,你可以通过迭代算法完成吗?这句话了。

本文的主要内容如下:

  • 题目定义
    • 上篇:二叉树前序、中序、后序遍历
    • 下篇:层序遍历、其他遍历相关题型
  • 解题思路:递归 + 迭代+ 莫里斯Morris遍历
  • 解题代码:Java + Python

注1:本文中的解题思路会尽量的全面,但是解题方法千变万化,有很多奇技淫巧我不会去介绍,大家有兴趣可以自行扩展学习。

注2:本文中的代码会尽量简单,易懂,并不会去追求极致的写法(比如:在一行内完成,使用各种非正式的库等)。

正文

二叉树的定义

最多有两棵子树的树被称为二叉树

二叉树下还有很多种特殊的二叉树,下方简单介绍几种常用的。

满二叉树

二叉树中所有非叶子结点的度都是2,且叶子结点都在同一层次上

完全二叉树(可以不满)

如果一个二叉树与满二叉树前m个节点的结构相同,这样的二叉树被称为完全二叉树。也就是说,如果把满二叉树从右至左、从下往上删除一些节点,剩余的结构就构成完全二叉树。

二叉搜索树

二叉查找树(BinarySearch Tree,也叫二叉搜索树,或称二叉排序树Binary Sort Tree)或者是一棵空树,或者是具有下列性质的二叉树:

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉排序树

二叉树前中后序遍历

遍历方法

前序遍历:根结点 —> 左子树 —> 右子树

中序遍历:左子树—> 根结点 —> 右子树

后序遍历:左子树 —> 右子树 —> 根结点

题目介绍

前序遍历

LeetCode题目地址:

https://leetcode-cn.com/problems/binary-tree-preorder-traversal/

输入: [1,null,2,3]  1\2/3 输出: [1,2,3]

中序遍历

LeetCode题目地址:

https://leetcode-cn.com/problems/binary-tree-inorder-traversal/

输入: [1,null,2,3]1\2/3输出: [1,3,2]

后序遍历

LeetCode题目地址:

https://leetcode-cn.com/problems/binary-tree-postorder-traversal/

输入: [1,null,2,3]  1\2/3 输出: [3,2,1]

解题思路详解与代码实现

二叉树的前中后序遍历,主要就是两种思路,一种是递归,一种是迭代。

如果看到这里还没有感觉,不用担心,先直接往下看,第一个代码(前序遍历的递归思路)会帮助你提升感觉。

递归思路

递归思路是最容易理解的思路,并且前中后序遍历都相同。

比如前序遍历,在递归的函数里,先往结果数组里加入根节点,然后加入根节点的左节点,然后加入根节点的右节点。最后所有递归的函数运行完毕,结果集就已经完成了。

中序和后序的思路相同,就不再赘述了。

前序遍历

Java:

class Solution {public List<Integer> preorderTraversal(TreeNode root) {List<Integer> result = new ArrayList<>();if (root == null) {return result;}preorder(root, result);return result;}private static List<Integer> preorder(TreeNode root, List<Integer> result) {if (root != null) {result.add(root.val);preorder(root.left, result);preorder(root.right, result);}return result;}
}

Python:

class Solution(object):def _preorderTraversal(self, root, result):if root:result.append(root.val)self._preorderTraversal(root.left, result)self._preorderTraversal(root.right, result)def preorderTraversal(self, root):""":type root: TreeNode:rtype: List[int]"""if root == None:return []result = []self._preorderTraversal(root, result)return result
中序遍历

Java:

class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> result = new ArrayList<>();if (root == null) {return result;}result = inorder(root, result);return result;}private static List<Integer> inorder(TreeNode root, List<Integer> result) {if (root != null) {inorder(root.left, result);result.add(root.val);inorder(root.right, result);}return result;}
}

Python:

class Solution(object):def generate(self, root, result):if root:self.inorder(root.left, list)result.append(root.val)self.inorder(root.right, list)def inorderTraversal(self, root):""":type root: TreeNode:rtype: List[int]"""if not root:return []result = []self.generate(root, result)return result
后序遍历

Java:

class Solution {public List<Integer> postorderTraversal(TreeNode root) {List<Integer> result = new ArrayList<>();if (root == null) {return result;}result = postorder(root, result);return result;}private static List<Integer> postorder(TreeNode root, List<Integer> result) {if (root != null) {postorder(root.left, result);postorder(root.right, result);result.add(root.val);}return result;}
}

Python:

class Solution(object):def _postorderTraversal(self, root, result):if root:self._postorderTraversal(root.left, result)self._postorderTraversal(root.right, result)result.append(root.val)def postorderTraversal(self, root):""":type root: TreeNode:rtype: List[int]"""if root == None:return []result = []self._postorderTraversal(root, result)return result

迭代思路

前序遍历

我们需要一个栈来完成遍历。

1.根节点入栈2.取出节点,值加入结果,然后先加右,后加左。3.重复2

这样就得到了 根节点——左子树——右子树 的遍历结果集。

Java:

来自官方题解

LinkedList<TreeNode> stack = new LinkedList<>();LinkedList<Integer> output = new LinkedList<>();if (root == null) {return output;}stack.add(root);while (!stack.isEmpty()) {TreeNode node = stack.pollLast();output.add(node.val);if (node.right != null) {stack.add(node.right);}if (node.left != null) {stack.add(node.left);}}return output;}

Python:

class Solution(object):def preorderTraversal(self, root):""":type root: TreeNode:rtype: List[int]"""ret = []stack = [root]while stack:node = stack.pop()if node:ret.append(node.val)if node.right:stack.append(node.right)if node.left:stack.append(node.left)return ret
中序遍历

还是使用一个栈来解决问题。

步骤如下:

					 1/   \2    3/   \  /   \4     5  6    7
  1. 我们将根节点1入栈,如果有左孩子,依次入栈,那么入栈顺序为:1,2,4。由于4的左子树为空,停止入栈,此时栈为{1,2,4}。

  2. 此时将4出栈,并遍历4,由于4也没有右孩子,那么根据中序遍历的规则,我们显然应该继续遍历4的父亲2,情况是这样。所以我们继续将2出栈并遍历2,2存在右孩子,将5入栈,此时栈为{1,5}。

  3. 5没有孩子,则将5出栈并遍历5,这也符合中序遍历的规则。此时栈为{1}。

  4. 1有右孩子,则将1出栈并遍历1,然后将右孩子3入栈,并继续以上三个步骤即可。

栈的变化过程:{1}->{1,2}->{1,2,4}->{1,2}->{1}->{1,5}->{1}->{}->{3}->{3,6}->{3}->{}->{7}->{}。

总结:从根节点遍历,先放入所有有左孩子的节点直到没有,然后出栈,出栈的时候就将出栈的数字放入结果集,然后看其有没有右孩子,有的话右孩子入栈。

Java:

官方题解

public class Solution {public List <Integer> inorderTraversal(TreeNode root) {List<Integer> res = new ArrayList<>();Stack<TreeNode> stack = new Stack<>();TreeNode curr = root;while (curr != null || !stack.isEmpty()) {while (curr != null) {stack.push(curr);curr = curr.left;}curr = stack.pop();res.add(curr.val);curr = curr.right;}return res;}
}

Python:

class Solution:# @param root, a tree node# @return a list of integersdef inorderTraversal(self, root):result = []stack = []while root or stack:if root:stack.append(root)root = root.leftelse:root = stack.pop()result.append(root.val)root = root.rightreturn result
后序遍历

将数组输出为右子树-左子树-根节点。最后,再将数组倒序输出,形成后序遍历。这样代码并不用很繁琐,也能做完迭代。

是不是似曾相识,没错,和前序遍历的迭代几乎一样,仅仅是先放右节点再放左节点变成了先放左节点再放右节点,然后倒序输出。

Java:

class Solution {public List<Integer> postorderTraversal(TreeNode root) {LinkedList<TreeNode> stack = new LinkedList<>();LinkedList<Integer> output = new LinkedList<>();if (root == null) {return output;}stack.add(root);while (!stack.isEmpty()) {TreeNode node = stack.pollLast();output.addFirst(node.val);if (node.left != null) {stack.add(node.left);}if (node.right != null) {stack.add(node.right);}}return output;}
}

Python:

class Solution(object):def postorderTraversal(self, root):""":type root: TreeNode:rtype: List[int]"""if root is None:return []ret = []stack = [root]while stack:node = stack.pop()if node:ret.append(node.val)if node.left:stack.append(node.left)if node.right:stack.append(node.right)return ret[::-1]

所以迭代方式,前后序是非常类似的,中序遍历可能需要单独理解下。

莫里斯遍历

二叉树常规的遍历方法是用递归来实现的,这种方法一般都需要O(n)的空间复杂度和O(n)的时间复杂度。而Morris方法实现的是O(1)的空间复杂度和O(n)的时间复杂度。

我们知道,遍历二叉树时,最大的难点在于,遍历到子节点的时候怎样重新返回到父节点(假设节点中没有指向父节点的p指针),由于不能用栈作为辅助空间。(不然就是普通迭代方法)。

为了解决这个问题,Morris方法用到了线索二叉树(threaded binary tree)的概念。在Morris方法中不需要为每个节点额外分配指针指向其前驱(predecessor)和后继节点(successor),只需要利用叶子节点中的左右空指针指向某种顺序遍历下的前驱节点或后继节点就可以了

听不懂没关系,下面使用中序遍历作为例子来理解莫里斯遍历到底是什么意思,例子来自LeetCode官方题解:

中序遍历
Step 1: 将当前节点current初始化为根节点
Step 2: While current不为空,若current没有左子节点a. 将current添加到输出b. 进入右子树,亦即, current = current.right
否则a. 在current的左子树中,令current成为最右侧节点的右子节点b. 进入左子树,亦即,current = current.left
      1/   \2     3/ \   /4   5 6

首先,1 是根节点,所以将 current 初始化为 1。1 有左子节点 2,current 的左子树是

     2/ \4   5

在此左子树中最右侧的节点是 5,于是将 current(1) 作为 5 的右子节点。令 current = cuurent.left (current = 2)。 现在二叉树的形状为:

 2
/ \
4   5\1\3/6

对于 current(2),其左子节点为4,我们可以继续上述过程

4\2\5\1\3/6

Java:

class Solution {public List < Integer > inorderTraversal(TreeNode root) {List < Integer > res = new ArrayList < > ();TreeNode curr = root;TreeNode pre;while (curr != null) {if (curr.left == null) {res.add(curr.val);curr = curr.right; // move to next right node} else { // has a left subtreepre = curr.left;while (pre.right != null) { // find rightmostpre = pre.right;}pre.right = curr; // put cur after the pre nodeTreeNode temp = curr; // store cur nodecurr = curr.left; // move cur to the top of the new treetemp.left = null; // original cur left be null, avoid infinite loops}}return res;}
}
前序遍历

理解了中序遍历,前序和后序遍历相对来说也就更容易理解了。所以前序和后序贴了思路,代码我也没自己写后submit,在这里就不放了。

算法的思路是从当前节点向下访问先序遍历的前驱节点,每个前驱节点都恰好被访问两次。

首先从当前节点开始,向左孩子走一步然后沿着右孩子一直向下访问,直到到达一个叶子节点(当前节点的中序遍历前驱节点),所以我们更新输出并建立一条伪边 predecessor.right = root 更新这个前驱的下一个点。如果我们第二次访问到前驱节点,由于已经指向了当前节点,我们移除伪边并移动到下一个顶点。

后序遍历

后续遍历稍显复杂,需要建立一个临时节点dump,令其左孩子是root。并且还需要一个子过程,就是倒序输出某两个节点之间路径上的各个节点。

步骤:

当前节点设置为临时节点dump。

  1. 如果当前节点的左孩子为空,则将其右孩子作为当前节点。

  2. 如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点。

    a) 如果前驱节点的右孩子为空,将它的右孩子设置为当前节点。当前节点更新为当前节点的左孩子。

    b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空。倒序输出从当前节点的左孩子到该前驱节点这条路径上的所有节点。当前节点更新为当前节点的右孩子。

  3. 重复以上1、2直到当前节点为空。

参考

  • https://leetcode-cn.com/problems/binary-tree-preorder-traversal/solution/leetcodesuan-fa-xiu-lian-dong-hua-yan-shi-xbian-2/
  • https://www.cnblogs.com/AnnieKim/archive/2013/06/15/morristraversal.html
  • https://blog.csdn.net/softwarex4/article/details/95986102

关注我

我是一名后端开发工程师。

主要关注后端开发,数据安全,爬虫,物联网,边缘计算等方向,欢迎交流。

各大平台都可以找到我

  • 微信公众号:后端技术漫谈
  • Github:@qqxx6661
  • CSDN:@Rude3knife
  • 知乎:@后端技术漫谈
  • 简书:@蛮三刀把刀
  • 掘金:@蛮三刀把刀

原创博客主要内容

  • 后端开发相关技术文章
  • Java面试知识点复习全手册
  • 设计模式/数据结构
  • LeetCode/剑指offer 算法题解析
  • SpringBoot/SpringCloud菜鸟入门实战系列
  • 爬虫相关技术文章
  • 逸闻趣事/好书分享/个人生活

个人公众号:后端技术漫谈

公众号:后端技术漫谈.jpg

如果文章对你有帮助,不妨收藏,投币,转发,在看起来~


http://chatgpt.dhexx.cn/article/YcWB3kwo.shtml

相关文章

二叉树遍历算法的应用

目录 一、二叉树遍历算法的应用——二叉树的创建 二、二叉树遍历算法的应用——复制二叉树 三、二叉树遍历算法的应用——计算二叉树的深度 四、二叉树遍历算法的应用——计算二叉树节点总数 五、二叉树遍历算法的应用——计算二叉树叶子节点数 一、二叉树遍历算法的应用—…

一文弄懂二叉树的三种遍历方式

关注公众号【高性能架构探索】&#xff0c;后台回复【pdf】&#xff0c;免费获取计算机必备经典书籍 俗话说&#xff1a;学如逆水行舟,不进则退;心似平原走马,易放难收。这句话对程序员而言&#xff0c;体会更深。这行已经越来越卷了&#xff0c;时刻准备着&#xff0c;&#x…

二叉树遍历算法

目录 先序遍历 中序遍历 后序遍历 层序遍历 938. 二叉搜索树的范围和 110. 平衡二叉树 114. 二叉树展开为链表 117. 填充每个节点的下一个右侧节点指针 II 116. 填充每个节点的下一个右侧节点指针 1&#xff0c;三种遍历都是先把二叉树的最左结点循环入栈(DFS迭代)&am…

二叉树的四种遍历算法(结构体数组)

一、二叉树的定义 以字符串的形式定义一棵二叉树的先序序列&#xff0c;若字符是‘#’&#xff0c;表示该二叉树是空树&#xff0c;否则该字符是相应结点的数据元素。 例&#xff1a;ABDG##HI####CE#J##F## 对应的二叉树&#xff1a; 思路讲解&#xff1a; 想要遍历二叉树&am…

二叉树的四种遍历方式

概要 树本身是一种简单化的图 &#xff1b; DFS对应前中后序遍历&#xff0c;BFS对应层序遍历 二叉树结构 struct treenode {int val;treenode *left;treenode *right;treenode() : val(0), left(nullptr), right(nullptr) {}treenode(int x) : val(x), left(nullptr), right(n…

针对Linux学习,值得阅读的五本书籍,不看可能错失机会

今天为了总结一些可以帮助各位在学习过程中提供帮助的一些书籍。 一.鸟叔的私房菜&#xff1a; 本书是知名度颇高的Linux入门书《鸟哥的Linux私房菜基础学习篇》的新版,而详细地介绍了Linux操作系统。全书分为五部分;第一部分部分着重说明计算机的基础知识、Linux的学习方法&a…

从零开始学习Linux(一)关闭虚拟机系统

关闭系统&#xff0c;需要输入如下命令 poweroff然而&#xff0c;你只能得到如下反馈 -bash: poweroff:command not found此项错误是因为poweroff命令是一个系统管理命令。执行此项命令需要高级使用者特权。 因此&#xff0c;为了关闭系统&#xff0c;我们首先需要切换到root…

个人随笔/小白应该如何学习Linux,我的一些心得分享.

大家好&#xff0c;今天给大家分享一下0基础的人如何入门Linux&#xff0c;此文来源&#xff1a;我在上班的路上看到一篇文章&#xff0c;也是写的0基础的人如何学习Linux的文章。当时我在想&#xff0c;我写博文一年多&#xff0c;都是相关Linux及Python等技术的文章&#xff…

Linux学习路线

&#xfeff;&#xfeff; 关于 Linux Linux 因其开源&#xff0c;免费&#xff0c;可裁剪&#xff0c;被应用到很多领域&#xff0c;尤其是嵌入式设备上。 Android 系统内核也是基于 Linux 的。 另外还有各种服务器和工作站也是用的 Linux。 什么是嵌入式设备&#xff1f;…

为什么要学习 Linux ????

目前企业中大量的使用Linux作为服务器&#xff0c;在以后你们就业后&#xff0c;会发现web服务器Tomcat ,jobss这一类都是搭建在linux上面的&#xff0c;后面我们需要学习的数据库mysql &#xff0c; oracle &#xff0c;db2&#xff0c; 或者greenplum这一类的&#xff0c;在企…

Linux 学习路线图

1.应用场景 更加高效地学习并达到运用Linux. 2.学习/操作 linux运维学习需要分为四个阶段&#xff1a;初级入门、中级进阶、高级提升、资深方向细化。 第一阶段&#xff1a;初级入门 初级阶段需要把linux学习路线搞清楚&#xff0c;任何学习都是循序渐进的&#xff0c;所以学…

从零入门机器学习之Linux系统详解

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,科大讯飞比赛第三名,CCF比赛第四名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

为什么要学习Linux?

对于一些偶然接触到Linux的人来说&#xff0c;好奇是对于这个陌生名词的的第一印象。也许这个名字经常出现在你所使用的教科书上&#xff0c;或者是一些技术性的文章上&#xff0c;你却不知其意&#xff0c;此时这个名字再次出现&#xff0c;你就更是好奇了&#xff0c;Linux到…

Linux学习总结

课程&#xff1a;Linux操作系统与应用 参考书&#xff1a;Linux从入门到精通、unix环境高级编程 学习linux之前必须要做好心理准备&#xff1a; 第一&#xff0c;要明白学好linux不是一件一蹴而就的事&#xff0c;一定要能坚持使用它&#xff0c;特别是在使用初期&#xff0c…

你知道如何学习Linux吗?

说起Linux&#xff0c;业内人士或者经常玩电脑&#xff0c;对计算机比较精通的应该是比较熟悉的&#xff0c;Linux是一个开源的操作系统&#xff0c;由于其安全性高&#xff0c;完全免费&#xff0c;高效性&#xff0c;稳定等优点&#xff0c;越来越受大众的欢迎&#xff0c;就…

学习linux的感受

学习前要 1.安装虚拟机或者自己买个云服务器 下载centOs然后将镜像装入系统 2.装入之后在自己的电脑下载Xshell和Xbox 3在自己windows系统下运行cmd拼一下自己的虚拟机或服务器测试两个机子网络是否相通&#xff0c;如果相通即可用Xshell进行远程登陆 成果: 今天学了vim与vi&…

初学者如何系统性地学习Linux?

作为一个大一的同学&#xff0c;可以采取下面的步骤进行系统的学习Linux。 1、选择一个发行版&#xff1a;对于初学者&#xff0c;推荐使用Ubuntu或者Linux Mint。Ubuntu适合新手&#xff0c;使用广泛&#xff0c;社区活跃&#xff0c;遇到问题容易找到解决方案。虽然你觉得Ub…

如何学习Linux

热热热 一、Linux大致要学习那些内容 1、Linux下的基本操作命令 2、Linux的各种配置 环境变量、网络的配置、服务的配置----常规而重要 3、Linux下搭建各种开发环境 例如&#xff1a; Javaee、大数据、Python等 4、能够写一些基本的shell脚本&#xff0c;对Linux系统进…

QTP基本使用1

目录 一、功能自动化 1、测试过程 2、录制类型 二、QTP基本使用1 1、【录制】 2、【运行】 3、【例 -- 录制编写记事本】 4、【设置】 三、上午程序脚本 四、test -- project 的比较 五、QTP基本使用2 1、导出test文件 2、导入test文件 3、查看帮助文档 4、修改…

QTP 脚本语言编写入门到精通(一)

飞机订票登陆系统flight 一、编写用户登录测试用例。 二、直接编写脚本 ****************** SystemUtil.Run PathFinder.Locate("..\samples\flight\app\flight4a.exe"),"",PathFinder.Locate("..\samples\flight\app"),"open" Syst…