今天毛毛张要分享的内容是LeetCode的刷题笔记,主要介绍的二叉树前序遍历、中序遍历、后序遍历和层序遍历思想和多种代码实现,二叉树的遍历对于二叉树后面的题目的实现具有很重要的意义
1.概述
LeetCode题目链接:
二叉树的遍历方式有多种,毛毛张今天在这里结合代码随想录的教程来分享一下二叉树的多种遍历方式及代码实现
二叉树主要有两种遍历方式:深度优先遍历和广度优先遍历
深度优先遍历: 树是图的一种特例(连通无环的图就是树),主要思路是从图中一个未访问的顶点 V 开始,沿着一条路一直走到底,然后从这条路尽头的节点回退到上一个节点,再从另一条路开始走到底…,不断递归重复此过程,直到所有的顶点都遍历完成,它的特点是不撞南墙不回头,先走完一条路,再换一条路继续走
- 前序遍历(递归法,迭代法)
- 中序遍历(递归法,迭代法)
- 后序遍历(递归法,迭代法)
广度优先遍历:一层一层的去遍历,指的是从图的一个未遍历的节点出发,先遍历这个节点的相邻节点,再依次遍历每个相邻节点的相邻节点
在深度优先遍历中:有三个顺序,前中后序遍历。这里前中后,其实指的就是中间节点的遍历顺序,只要大家记住 前中后序指的就是中间节点的位置就可以了。看如下中间节点的顺序,就可以发现,中间节点的顺序就是所谓的遍历方式:
- 前序遍历:中左右
- 中序遍历:左中右
- 后序遍历:左右中
大家可以对着如下图,看看自己理解的前后中序有没有问题:

在做二叉树的相关题目的时候,在使用深度优先遍历的时候一般会使用递归方法来实现
而栈其实就是一种递归的一种实现结构,前中后序遍历的逻辑其实都是可以借助栈使用递归的方式来实现的,由此衍生出一种迭代法
而广度优先遍历的实现一般使用队列来实现,这也是队列先进先出的特点所决定的,因为需要先进先出的结构,才能一层一层的来遍历二叉树。
下面开始逐一介绍各种方法的代码实现,虽然方法名被概括为精炼的几个字,但是在具体的代码实现的时候,可能根据书写习惯的不同又会分成不同的实现方法,所以毛毛张在这里介绍几种常见的书写方法。
2.深度优先遍历:递归实现
深度优先遍历的递归实现比较简单,毛毛张在这里不做过多的赘述,只介绍一下代码随想录中的递归方法论,记住这个方法论有助于在解题的过程中不会漏掉特殊情况或者某一步骤。
- 递归的方法论:
- 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
- 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
- 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
2.1 前序遍历(递归法)
方法1:新建递归函数
实现思路: 在力扣的模板中会默认给你提供一个前序遍历的函数体,该方法在书写的时候没有使用默认的函数体作为递归函数,因为这个函数已经给你规定好了递归函数的形参和返回值,不利于理解,毛毛张在这里是单独新建一个递归函数供其调用,这种方式更加容易理解。
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
class Solution { public List<Integer> preorderTraversal(TreeNode root) { List list = new ArrayList<Integer>();
preOrder(root,list);
return list; }
public void preOrder(TreeNode node,List list){ if(node == null){ return; }
list.add(node.val); preOrder(node.left,list); preOrder(node.right,list);
}
}
|
方法2:使用默认递归函数
为了完整性,毛毛张当然也要在这里给出使用提供的默认函数作为递归函数的代码啦!
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> list = new ArrayList<>(); if(root == null){ return list; }
list.add(root.val); List<Integer> leftList = preorderTraversal(root.left); list.addAll(leftList); List<Integer> rightList = preorderTraversal(root.right); list.addAll(rightList);
return list; } }
|
错解:
方法2这里直接使用题目帮你确定的形参和返回值
同时在确定终止条件的时候需要注意要返回一个空列表,不要写成return null
大家可用在leetcode中实战对比一下下面这写法
错解代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> list = new ArrayList<>();
if(root == null){ return null; }
list.add(root.val); List<Integer> leftList = preorderTraversal(root.left); list.addAll(leftList); List<Integer> rightList = preorderTraversal(root.right); list.addAll(rightList);
return list; } }
|
2.2 中序遍历(递归法)
方法1:新建递归函数
实现思路: 在立扣的模板中会默认给你提供一个中序遍历的函数体,该方法在书写的时候没有使用默认的函数体作为递归函数,因为这个函数已经给你规定好了递归函数的形参和返回值,不利于理解,毛毛张在这里是单独新建一个递归函数供其调用,这种方式更加容易理解。
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> list = new ArrayList<>();
inOrder(root,list);
return list; }
public void inOrder(TreeNode node,List<Integer> list){ if(node == null){ return; }
inOrder(node.left,list); list.add(node.val); inOrder(node.right,list); } }
|
方法2:使用默认递归函数
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> list = new ArrayList<>();
if(root == null){ return list; }
List<Integer> leftList = inorderTraversal(root.left); list.addAll(leftList); list.add(root.val); List<Integer> rightList = inorderTraversal(root.right); list.addAll(rightList);
return list; } }
|
2.3 后序遍历(递归法)
方法1:新建递归函数
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> list = new ArrayList<>();
postOrder(root,list);
return list; }
public void postOrder(TreeNode node,List<Integer> list){ if(node == null){ return; }
postOrder(node.left,list); postOrder(node.right,list); list.add(node.val); } }
|
方法2:使用默认递归函数
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> list = new ArrayList<>();
if(root == null){ return list; }
List<Integer> leftList = postorderTraversal(root.left); list.addAll(leftList);
List<Integer> rightList = postorderTraversal(root.right); list.addAll(rightList);
list.add(root.val);
return list; } }
|
3.深度优先遍历:迭代法
3.1 原理分析
3.1.1 前序遍历与中序遍历
3.1.2 后序遍历
后序遍历的非递归实现是三种遍历方法中最难的,因为在后序遍历中,要保证左孩了和右孩子都已被访问并且左孩子在右孩子前访问才能访问根结点,这就为流程的控制带来了难题。
算法思想:后序非递归遍历二叉树是先访问左子树,再访问右子树,最后访问根结点
- 第1步:沿着根的左孩子,依次入栈,直到左孩子为空,此时栈内元素依次为ABD
- 第2步:读栈顶元素,若其右孩子不空且未被访问过,将右子树转执行第1步;否则,栈顶元素出栈并访问
代码实现细节:在上述思想的第2步中,必须分清返回时是从左子树返回的还是从右子树返回的,因此设定一个辅助指针r,指向最近访问过的结点。也可在结点中增加一个标志域,记录是否已被访问
实例过程分析:
- 栈顶D的右孩子为空,出栈并访问,它是后序序列的第一个结点;
- 栈顶B的右孩子不空且未被访问过,E入栈,栈顶E的左右孩子均为空,出栈并访问;
- 栈顶B的右孩子不空但已被访问,B出栈并访问;
- 栈项A的右孩子不空且未被访问过,C入栈,栈项C的左右孩子均为空,出栈并访问;
- 栈顶A的右孩子不空但已被访问,A出栈并访问,由此得到后序序列DEBCA
我们再来分析后序遍历的第二种实现方式:要实现前面的这个逻辑还是比较复杂的,先序遍历是中左右,后续遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转结果数组,输出的结果顺序就是左右中了

3.2 前序遍历(迭代法)
- 分析:前序遍历是中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子
- 为什么要先加入 右孩子,再加入左孩子呢? 因为这样出栈的时候才是中左右的顺序。
方法1:单层循环
单层循环和双层循环有细微的差别,大家可以自己看代码,根据自己的习惯来写
实现1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> list = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); TreeNode cur = root; while(cur != null || !stack.isEmpty()){ if(cur != null){ list.add(cur.val); stack.push(cur); cur = cur.left; } else{ cur = stack.pop(); cur = cur.right; } }
return list; } }
|
实现2:
只有前序遍历能这样实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> list = new ArrayList<>(); if(root == null){ return list; } Stack<TreeNode> stack = new Stack<>(); stack.push(root); while(!stack.isEmpty()){ TreeNode node = stack.pop(); list.add(node.val); if(node.right != null){ stack.push(node.right); } if(node.left != null){ stack.push(node.left); } } return list; } }
|
方法2:双层循环
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> list = new ArrayList<>(); Deque<TreeNode> stack = new LinkedList<>(); TreeNode cur = root; while(cur != null || !stack.isEmpty()){ while(cur != null){ list.add(cur.val); stack.push(cur); cur = cur.left; } cur = stack.pop(); cur = cur.right; } return list; } }
|
3.3 中序遍历(迭代法)
单层循环和双层循环有细微的差别,大家可以自己看代码,根据自己的习惯来写
方法1:单层循环
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> list = new ArrayList<>(); Deque<TreeNode> stack = new LinkedList<>(); TreeNode cur = root; while(cur != null || !stack.isEmpty()){ if(cur != null){ stack.push(cur); cur = cur.left; } else{ cur = stack.pop(); list.add(cur.val); cur = cur.right; } } return list; } }
|
方法2:双层循环
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> list = new ArrayList<>(); Deque<TreeNode> stack = new LinkedList<>(); TreeNode cur = root; while(cur != null || !stack.isEmpty()){ while(cur != null){ stack.push(cur); cur = cur.left; } cur = stack.pop(); list.add(cur.val); cur = cur.right; } return list; } }
|
3.4 后序遍历(迭代法)
单层循环和双层循环有细微的差别,大家可以自己看代码,根据自己的习惯来写
方法1:单层循环
这个代码是使用列表反转这个技巧的代码
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> list = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); TreeNode cur = root; while(cur != null || !stack.isEmpty()){ if(cur != null){ list.add(cur.val); stack.push(cur); cur = cur.right; } else{ cur = stack.pop(); cur = cur.left; } } Collections.reverse(list); return list; } }
|
方法2:双层循环(反转)
这个代码是使用列表反转这个技巧的代码
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> list = new ArrayList<>(); Deque<TreeNode> stack = new LinkedList<>(); TreeNode cur = root; while(cur != null || !stack.isEmpty()){ while(cur != null){ list.add(cur.val); stack.push(cur); cur = cur.right; } cur = stack.pop(); cur = cur.left; } Collections.reverse(list); return list; } }
|
方法3:双层循环(不反转)
这个代码是后序遍历真实的逻辑代码,没有使用反转这个技巧
code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public List<Integer> postorderTraversal(TreeNode root) { List<Integer> ret = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); TreeNode cur = root; TreeNode prev = null; while(cur != null || !stack.empty()) { while(cur != null) { stack.push(cur); cur = cur.left; } cur = stack.peek(); if(cur.right == null || cur.right == prev) { stack.pop(); ret.add(cur.val); prev = cur; }else{ cur = cur.right; } } return ret; }
|
4.深度优先遍历:统一迭代法
前序遍历
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> list = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); if(root != null) stack.push(root); while(!stack.isEmpty()){ TreeNode node = stack.peek(); if(node != null){ stack.pop(); if(node.right != null) stack.push(node.right); if(node.left != null) stack.push(node.left); stack.push(node); stack.push(null); } else { stack.pop(); node = stack.pop(); list.add(node.val); } } return list; } }
|
中序遍历
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> list = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); if(root != null) stack.push(root); while(!stack.isEmpty()){ TreeNode node = stack.peek(); if(node != null){ stack.pop(); if(node.right != null) stack.push(node.right); stack.push(node); stack.push(null); if(node.left != null) stack.push(node.left); } else { stack.pop(); node = stack.pop(); list.add(node.val); } } return list; } }
|
后序遍历
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> list = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); if(root != null) stack.push(root); while(!stack.isEmpty()){ TreeNode node = stack.peek(); if(node != null){ stack.pop(); stack.push(node); stack.push(null); if(node.right != null) stack.push(node.right); if(node.left != null) stack.push(node.left); } else { stack.pop(); node = stack.pop(); list.add(node.val); } } return list; } }
|
5.广度优先遍历
- 二叉树的广度优先遍历也叫做层序遍历。
- 层序遍历一个二叉树,就是从左到右一层一层的去遍历二叉树。
- 需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。
方法1:递归法
该方法使用的是递归
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class Solution { List<List<Integer>> result = new ArrayList<>(); public List<List<Integer>> levelOrder(TreeNode root) { levelOrderTraverse(root,0); return result; }
public void levelOrderTraverse(TreeNode node,int depth){ if(node == null) return; depth++; if(result.size() < depth){ List<Integer> list = new ArrayList<>(); result.add(list); } result.get(depth - 1).add(node.val); levelOrderTraverse(node.left,depth); levelOrderTraverse(node.right,depth); } }
|
方法2:迭代法
该方法使用的迭代法,需要借助队列来实现
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| class Solution { public List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> ret = new ArrayList<List<Integer>>();
if(root == null){ return ret; }
Queue<TreeNode> queue = new LinkedList<TreeNode>(); queue.offer(root); while(!queue.isEmpty()){ List<Integer> level = new ArrayList<Integer>(); int currentLevelSize = queue.size(); for(int i = 1;i <= currentLevelSize; ++i){ TreeNode node = queue.poll(); level.add(node.val);
if(node.left != null){ queue.offer(node.left); }
if(node.right != null){ queue.offer(node.right); }
} ret.add(level); } return ret; } }
|
方法1:递归法
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class Solution { List<List<Integer>> result = new ArrayList<>(); public List<List<Integer>> levelOrder(TreeNode root) { levelOrderTraverse(root,0); Collections.reverse(ret); return result; }
public void levelOrderTraverse(TreeNode node,int depth){ if(node == null) return; depth++; if(result.size() < depth){ List<Integer> list = new ArrayList<>(); result.add(list); } result.get(depth - 1).add(node.val); levelOrderTraverse(node.left,depth); levelOrderTraverse(node.right,depth); } }
|
方法2:迭代法
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| class Solution { public List<List<Integer>> levelOrderBottom(TreeNode root) { List<List<Integer>> ret = new ArrayList<List<Integer>>();
if(root == null){ return ret; }
Queue<TreeNode> queue = new LinkedList<TreeNode>(); queue.offer(root); while(!queue.isEmpty()){ List<Integer> level = new ArrayList<Integer>(); int currentLevelSize = queue.size(); for(int i = 0;i < currentLevelSize;i++){ TreeNode node = queue.poll(); level.add(node.val);
if(node.left != null){ queue.offer(node.left); }
if(node.right != null){ queue.offer(node.right); }
} ret.add(level); }
Collections.reverse(ret); return ret; } }
|
5.3 和层序遍历相关的十一道题目
参考文献
https://zhuanlan.zhihu.com/p/692543960
https://www.51cto.com/article/614590.html
2
3