二叉树:深度优先和广度优先遍历
章内容为代码随想录二叉树遍历方式总结,具体内容看:代码随想录——二叉树理论基础
二叉树的遍历方式
二叉树的遍历方式大体分为深度优先和广度优先遍历。
深度优先遍历包含以下几种,都包含有递归和迭代的实现方式:
- 前序遍历
- 中序遍历
- 后序遍历
广度优先遍历一般就是层次遍历,掌握迭代法。
图像来自代码随想录
实现方式
深度优先遍历一般采用递归的方式实现,同时也可以借助栈来实现非递归的遍历。
广度优先遍历一般采用队列来实现。
递归方式遍历
写递归函数需要确定递归三要素:
- 递归函数的参数和返回值
像以下这些简单的遍历,就不需要设置什么返回值,因此返回值为void
。参数则是传入树的根节点,以用于遍历。
void dfs(TreeNode node)
- 终止条件
当遍历到的节点是空节点,就返回
if(cur==null)return;
- 单层递归的逻辑
在这里,前序、中序和后序遍历的递归的逻辑几乎都是一样的,只是访问当前节点的顺序不同,具体看下面的代码。
前序遍历
public void dfs(TreeNode node){
if(node==null){
return;
}
ans.add(node.val);
dfs(node.left);
dfs(node.right);
}
中序遍历
public void dfs(TreeNode node){
if(node==null)return;
dfs(node.left);
ans.add(node.val);
dfs(node.right);
}
后序遍历
public void dfs(TreeNode node){
if(node==null)return;
dfs(node.left);
dfs(node.right);
ans.add(node.val);
}
可以发现这几个遍历方式就是在访问当前节点的顺序有所不同。具体有以下几道题目:
- 144.二叉树的前序遍历
- 94.二叉树的中序遍历
- 145.二叉树的后序遍历
迭代方式遍历
采用迭代的方式进行遍历,主要是要采用栈这个数据结构。
前序遍历
迭代方式的前序遍历我觉得carl哥的图做的很清楚,看一遍应该就懂了。具体看:迭代遍历
前序遍历的遍历顺序是中、左、右。当访问“中”的时候,需要将这个节点弹出,然后将其右子树和左子树按顺序压入。
至于为什么是先右后左的顺序入栈,这是因为栈是先进后出啊。栈的先进后出决定了子树需要按照先右后左的顺序进入,才可以先访问左节点。(具体还是看动图,非常清楚)
同时还要注意对于空节点的处理。递归的时候对于空节点的处理是,碰到了就返回。那迭代方式怎么处理空节点呢
正确的做法是,只有不空的时候才将节点入栈。
迭代方式何时退出循环?
因为用栈进行前序遍历,当栈空,说明所有节点都遍历完了。
梳理以下迭代前序遍历的流程如下
-
将根节点入栈
-
若栈不空则执行:
- 弹出栈顶元素(访问中央节点)
- 如果右子树非空,则右子树入栈
- 如果左子树非空,则左子树入栈
以144.二叉树的前序遍历为例,迭代前序遍历的代码为:
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> ans=new ArrayList<>();
if(root==null)return ans;
Deque<TreeNode> stack=new LinkedList<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode cur=stack.pop();
ans.add(cur.val);
if(cur.right!=null){
stack.push(cur.right);
}
if(cur.left!=null){
stack.push(cur.left);
}
}
return ans;
}
后序遍历
后序遍历和前序遍历有一定的相似性,因此先讨论后序遍历。
后序遍历是按照左右中的方式进行的。在迭代过程中可以按照中右左的方式遍历(对比前序遍历迭代过程,就是在压入左右子节点的顺序不同)。然后对遍历访问的结果进行翻转,就是后序遍历。
以145.二叉树的后序遍历为例子,迭代后序遍历的代码为:
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ans=new ArrayList<>();
if(root==null)return ans;
Deque<TreeNode> stack=new LinkedList<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode cur=stack.pop();
ans.add(cur.val);
if(cur.left!=null)stack.push(cur.left);
if(cur.right!=null)stack.push(cur.right);
}
Collections.reverse(ans);
return ans;
}
一张潦草的手写
中序遍历
由于前面两种遍历方式,都是访问中间节点,然后处理中间节点。因此访问和处理的顺序是一致的。而中序遍历不一致。
对此,需要一个指针用于访问节点,而栈则暂时存储节点,用于处理元素(就是指弹出,再访问其左右孩子的操作)。
感觉中序遍历还是比较不好理解
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> ans=new ArrayList<>();
if(root==null)return ans;
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();
ans.add(cur.val);
cur=cur.right;
}
}
return ans;
}
层序遍历
层序遍历采用队列实现。访问到哪一个节点,就把它的左右子树(如果有)加入到队尾。
以102.二叉树的层序遍历为例
public class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> ans=new ArrayList<>();
Deque<TreeNode> queue=new LinkedList<>();
if(root!=null)queue.offer(root);
while(!queue.isEmpty()){
int size=queue.size();
List<Integer> list=new ArrayList<>();
for(int i=0;i<size;i++){
TreeNode cur=queue.pop();
list.add(cur.val);
if(cur.left!=null)queue.offer(cur.left);
if(cur.right!=null)queue.offer(cur.right);
}
ans.add(list);
}
return ans;
}
}
层序遍历基本都是这个模板,根据不同的要求会稍做改变。
到此为止。二叉树的基本遍历就结束了。个人感觉层序遍历是最容易理解的。而深度优先遍历,既有递归法(简单),又有迭代法(借助栈),且中序遍历的迭代方式又与前序和后序不大一样,因此要时常回顾一下。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY