news 2026/5/26 2:15:20

【码道初阶】【Leetcode94144145】二叉树的前中后序遍历(非递归版):显式调用栈的优雅实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【码道初阶】【Leetcode94144145】二叉树的前中后序遍历(非递归版):显式调用栈的优雅实现

用一个栈搞定二叉树前/中/后序遍历(非递归版):把递归“翻译”成 while 循环

很多人写递归遍历很顺手,但一到非递归就开始迷糊:栈怎么压?什么时候弹?为什么后序还要prev

其实核心只有一句话:

非递归遍历 = 用显式栈模拟递归调用栈,再用“访问时机”决定前/中/后序。

三种遍历的区别不在“走法”,而在“什么时候把节点加入结果”。

  • 前序:第一次见到节点就访问(根最早)
  • 中序:左边走到底后,弹栈时访问(根居中)
  • 后序:左右都处理完,最后才访问(根最晚)(所以需要额外信息判断“右子树是否已经处理过”)

下面按你给的三段代码逐段讲。


0. 先统一一个“心智模型”:栈里装的是什么?

递归遍历时,每次函数调用会把“当前节点”和“接下来要做的事”压到调用栈里。
非递归就是把“当前节点”手动压到stack,并用cur指针控制下一步去哪。

常见骨架长这样:

while(cur!=null||!stack.isEmpty()){while(cur!=null){stack.push(cur);cur=cur.left;}// 到这里说明:左链走到底了TreeNodetop=stack.pop()or stack.peek();// ...访问 or 转向右子树...}

差别在:

  • 前序:压栈时就访问
  • 中序:弹栈时访问
  • 后序:满足“左右都完成”才弹栈并访问

1) 中序遍历(左-根-右)非递归:最经典模板

中序代码:

classSolution{publicList<Integer>inorderTraversal(TreeNoderoot){//左根右Stack<TreeNode>stack=newStack<>();List<Integer>list=newArrayList<>();TreeNodecur=root;while(cur!=null||!stack.isEmpty()){while(cur!=null){stack.push(cur);cur=cur.left;}TreeNodetop=stack.pop();list.add(top.val);cur=top.right;}returnlist;}}

过程讲解(像在走图)

Step A:一路向左“钻到底”,沿途压栈

while(cur!=null){stack.push(cur);cur=cur.left;}

这一步把从当前节点出发的“左链”全部压栈。
压栈顺序大概就是:根、根的左、左的左……

Step B:左边到头了,开始“回溯”

TreeNodetop=stack.pop();list.add(top.val);cur=top.right;
  • pop()代表:递归里“左子树做完了,回到当前节点”
  • 此时访问top,就实现了“左-根”
  • 然后cur = top.right转向右子树,继续同样流程

为什么是中序?

因为访问发生在左子树处理完之后、右子树开始之前,刚好是“左根右”的根位置。

✅ 这份代码可以当作中序非递归的“标准答案模板”。


2) 后序遍历(左-右-根)非递归:关键在prev

后序代码:

classSolution{publicList<Integer>postorderTraversal(TreeNoderoot){List<Integer>list=newArrayList<>();if(root==null)returnlist;Stack<TreeNode>stack=newStack<>();TreeNodecur=root;TreeNodeprev=null;while(cur!=null||!stack.isEmpty()){while(cur!=null){stack.push(cur);cur=cur.left;}TreeNodetop=stack.peek();if(top.right==null||top.right==prev){list.add(top.val);stack.pop();prev=top;}else{cur=top.right;}}returnlist;}}

为什么后序比中序难?

后序要“根最后访问”。
但当左链走到底时,栈顶节点的左子树确实做完了——可右子树可能还没做
所以不能像中序那样直接pop()访问。

这就是prev的意义:

prev记录“刚刚完成访问(出栈)的节点”,用它判断右子树是不是已经处理过。

过程拆解(核心分支只看这一段)

TreeNodetop=stack.peek();if(top.right==null||top.right==prev){// 右子树为空 或 右子树刚处理完visit(top);pop(top);prev=top;}else{// 右子树存在且没处理cur=top.right;}

情况 1:top.right == null

右子树为空,那就说明:

  • 左子树已经完成(因为能来到 peek)
  • 右子树不存在
  • 所以当前节点可以访问(根最后)

情况 2:top.right == prev

这句是精髓:
“栈顶节点的右孩子刚刚被访问完”,说明右子树已经处理完毕。
于是当前节点也满足“左右都完成”,可以访问并出栈。

情况 3:右子树存在且未处理

这时候不能访问根,必须先去右子树:

cur=top.right;

然后循环会再次走 “一路向左压栈”,把右子树的左链压进去。

为什么这一定是后序?

因为每个节点只有在左子树完成右子树完成之后才会被访问,根必然最后。

⭐ 易错点重点标识

  • 如果没有prev,很容易在“从右子树回来”时无法判断是否该访问根,导致反复进入右子树或死循环。
  • peek()必须配合条件判断;这里不能直接pop(),否则会提前访问根,顺序变坏。

3) 前序遍历(根-左-右)非递归:访问时机前移

前序代码:

classSolution{publicList<Integer>preorderTraversal(TreeNoderoot){List<Integer>list=newArrayList<>();if(root==null)returnlist;Stack<TreeNode>stack=newStack<>();TreeNodecur=root;stack.push(cur);while(!stack.isEmpty()){while(cur!=null){stack.push(cur);list.add(cur.val);cur=cur.left;}TreeNodetop=stack.pop();cur=top.right;}returnlist;}}

先讲它的核心思想

前序要求“根最先访问”。
所以访问时机要比中序更早:第一次遇到节点就访问

你这份代码确实做到了:在压栈时就list.add(cur.val)

while(cur!=null){stack.push(cur);list.add(cur.val);cur=cur.left;}

然后左链走完,弹一个节点出来,转向它的右子树:

TreeNodetop=stack.pop();cur=top.right;

但这里有个小提醒(实现细节)

这段代码里有一行:

stack.push(cur);// 进入 while 前又 push 了一次 root

而在循环里又stack.push(cur),因此根节点会被压栈两次(不过访问只发生一次,所以结果通常不受影响,但栈操作会冗余,逻辑也不够干净)。

更常见、更“干净”的前序非递归写法通常是下面两种之一:

写法 A:一路向左,访问并压栈(不需要额外先 push)

publicList<Integer>preorderTraversal(TreeNoderoot){List<Integer>res=newArrayList<>();Deque<TreeNode>stack=newArrayDeque<>();TreeNodecur=root;while(cur!=null||!stack.isEmpty()){while(cur!=null){res.add(cur.val);// 前序:先访问stack.push(cur);cur=cur.left;}TreeNodetop=stack.pop();cur=top.right;}returnres;}

写法 B:标准“栈模拟”模板(弹出就访问,先压右再压左)

publicList<Integer>preorderTraversal(TreeNoderoot){List<Integer>res=newArrayList<>();if(root==null)returnres;Deque<TreeNode>stack=newArrayDeque<>();stack.push(root);while(!stack.isEmpty()){TreeNodenode=stack.pop();res.add(node.val);if(node.right!=null)stack.push(node.right);if(node.left!=null)stack.push(node.left);}returnres;}

两种都对。写法 A 和你中序的骨架更一致,适合“统一模板记忆”;写法 B 很直观,适合初学者。


4) 三种遍历的对照总结:差别只在“访问时机”

把三段代码放在一起看,其实只改了一个点:什么时候把top.val加进结果。

遍历顺序非递归共同骨架访问时机
前序根-左-右左链压栈 + 转右压栈/第一次遇到节点时访问
中序左-根-右左链压栈 + 转右弹栈时访问(左完成后)
后序左-右-根左链压栈 + 条件转右左右都完成时访问(靠prev判断)

5) 最容易踩的坑(建议贴在笔记顶部)

  1. 后序一定要有“右子树是否处理完”的判断
    没有prev或等价机制,极容易死循环或顺序错。

  2. 中序的关键动作是:pop 后访问,再转 right
    少一步都会乱序。

  3. 前序的关键是:访问要发生在走左之前
    add放到 pop 后,就变成中序/后序味道了。

  4. 尽量用ArrayDeque代替Stack(工程上更推荐)
    Stack是老类(继承自 Vector),同步开销大;Deque更现代更快。算法逻辑不变。


结尾:把递归“翻译”成迭代的诀窍

真正的诀窍不是背代码,而是把递归的三件事想明白:

  • 递归“往下走” → 用curwhile(cur != null)模拟
  • 递归“回到父节点” → 用stack保存路径
  • 前/中/后序的区别 →访问发生在:下潜前 / 回溯时 / 左右都完成后

理解到这层,换题、换写法都不会慌。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/26 4:47:54

实时人脸替换不再是梦:FaceFusion镜像全面支持流媒体处理

实时人脸替换不再是梦&#xff1a;FaceFusion镜像全面支持流媒体处理在直播带货、虚拟主播和远程会议日益普及的今天&#xff0c;观众早已不满足于“只是看到人”——他们想要更酷、更个性、更具沉浸感的视觉体验。而在这股浪潮背后&#xff0c;一个曾属于科幻电影的技术正悄然…

作者头像 李华
网站建设 2026/5/25 21:53:56

Langchain-Chatchat与Llama3集成:如何高效调用GPU算力?

Langchain-Chatchat与Llama3集成&#xff1a;如何高效调用GPU算力&#xff1f; 在企业智能化转型的浪潮中&#xff0c;一个现实问题日益凸显&#xff1a;如何让AI既足够聪明&#xff0c;又不泄露核心数据&#xff1f;尤其是在金融、医疗这类对隐私要求极高的行业&#xff0c;把…

作者头像 李华
网站建设 2026/5/26 3:41:22

论文AI率高达100%还能降下来吗?一篇文章教会你去ai痕迹

一、为什么我的论文总被标"AI生成"&#xff1f;你是不是也遇到这些崩溃瞬间... "明明自己改了三遍&#xff0c;维普查重还是显示AIGC率35%..." "导师指着查重报告问&#xff1a;这段是不是ChatGPT写的&#xff1f;" "答辩在即&#xff0c;…

作者头像 李华
网站建设 2026/5/26 8:00:26

2026年如何有效降AI率,顺利通过AI痕迹查重?

一、为什么我的论文总被标"AI生成"&#xff1f;你是不是也遇到这些崩溃瞬间... "明明自己改了三遍&#xff0c;维普查重还是显示AIGC率35%..." "导师指着查重报告问&#xff1a;这段是不是ChatGPT写的&#xff1f;" "答辩在即&#xff0c;…

作者头像 李华
网站建设 2026/5/26 21:33:52

用deepseek写的文章查重AI率很高?有什么办法降下来?

一、为什么我的论文总被标"AI生成"&#xff1f;你是不是也遇到这些崩溃瞬间... "明明自己改了三遍&#xff0c;维普查重还是显示AIGC率35%..." "导师指着查重报告问&#xff1a;这段是不是ChatGPT写的&#xff1f;" "答辩在即&#xff0c;…

作者头像 李华
网站建设 2026/5/26 4:48:35

Langchain-Chatchat在金融行业知识库中的应用实践

Langchain-Chatchat在金融行业知识库中的应用实践 在某城商行的一次内部合规培训中&#xff0c;一位新入职的信贷员提出了一个常见但棘手的问题&#xff1a;“个人经营贷客户需要提供哪些材料&#xff1f;”以往&#xff0c;这个问题可能需要翻阅几十页PDF文件、咨询老同事&…

作者头像 李华