当前位置: 首页 > news >正文

【LeetCode刷题日记】掌握二叉树遍历:栈实现的三种绝妙方法

🔥个人主页:北极的代码(欢迎来访)
🎬作者简介:java后端学习者
❄️个人专栏:苍穹外卖日记,SSM框架深入,JavaWeb
命运的结局尽可永在,不屈的挑战却不可须臾或缺!

前言:

我们在前面提到了,递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。

此时大家应该知道我们用栈也可以是实现二叉树的前后中序遍历了。

摘要:

本文介绍了使用栈实现二叉树三种遍历方式的迭代算法。

前序遍历通过"根→右→左"的入栈顺序实现;

中序遍历需要指针辅助,先一路向左压栈再处理节点;

后序遍历采用"根→右→左"顺序处理后反转结果。

三种方法对比:前序遍历访问与处理顺序一致,无需指针;中序遍历需要指针跟踪未处理节点;后序遍历通过反转前序变体实现。这些方法通过显式栈替代递归隐式栈,均能达到O(n)时间复杂度,是面试常考的二叉树遍历实现方案。

前序遍历(根 → 左 → 右)

思路

  • 访问和处理顺序一致:先根节点 → 再左 → 再右。

  • 用栈来模拟递归:先压右孩子,再压左孩子,这样出栈时先左后右。

步骤

  1. 根节点入栈。

  2. 循环直到栈为空:

    • 弹出栈顶 → 处理(加入结果)。

    • 右孩子非空 → 入栈。

    • 左孩子非空 → 入栈。

java public List<Integer> preorderTraversal(TreeNode root) { List<Integer> result = new ArrayList<>(); if (root == null) return result; Stack<TreeNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode node = stack.pop(); result.add(node.val); // 中 if (node.right != null) stack.push(node.right); // 右 if (node.left != null) stack.push(node.left); // 左 } return result; }

二、中序遍历(左 → 根 → 右)

为了解释清楚,我说明一下 刚刚在迭代的过程中,其实我们有两个操作:

  1. 处理:将元素放进result数组中
  2. 访问:遍历节点

分析一下为什么刚刚写的前序遍历的代码,不能和中序遍历通用呢,因为前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,所以刚刚才能写出相对简洁的代码,因为要访问的元素和要处理的元素顺序是一致的,都是中间节点。

那么再看看中序遍历,中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。

那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。

思路

  • 访问顺序:从根开始一直向左走到底。

  • 处理顺序:最左的子节点先处理,然后根,然后右子树。

  • 访问和处理顺序不一致→ 需要指针 + 栈。

步骤

  1. 指针cur指向根。

  2. 循环条件:cur != null或栈非空。

    • 如果cur != null:入栈 + 往左。

    • 否则(cur 为空):

      • 弹出栈顶(处理)。

      • 指针指向右孩子。

java public List<Integer> inorderTraversal(TreeNode root) { List<Integer> result = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); TreeNode cur = root; while (cur != null || !stack.isEmpty()) { if (cur != null) { stack.push(cur); cur = cur.left; // 左 } else { cur = stack.pop(); result.add(cur.val); // 中 cur = cur.right; // 右 } } return result; }

三、后序遍历(左 → 右 → 根)

思路

  • 利用前序遍历的变体:

    • 前序:根 → 左 → 右(栈:先右后左)。

    • 变体:根 → 右 → 左(栈:先左后右)。

  • 最后将结果反转→ 得到左 → 右 → 根。

步骤

  1. 根入栈。

  2. 循环:

    • 弹出栈顶 → 加入结果。

    • 先压左孩子,再压右孩子(保证出栈顺序是“根→右→左”)。

  3. 结果反转。

java public List<Integer> postorderTraversal(TreeNode root) { List<Integer> result = new ArrayList<>(); if (root == null) return result; Stack<TreeNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode node = stack.pop(); result.add(node.val); if (node.left != null) stack.push(node.left); if (node.right != null) stack.push(node.right); } Collections.reverse(result); return result; }

四、关键对比总结(面试常问)

遍历方式访问与处理顺序是否一致是否需要指针核心技巧
前序✅ 一致先右后左入栈
中序❌ 不一致指针一路向左,再处理
后序❌ 不一致反向收集 + 反转结果

结语:如果对你有帮助,请点赞,关注,收藏,你的支持就是我最大的鼓励!

http://www.jsqmd.com/news/755027/

相关文章:

  • 多目标优化与并行枚举算法(PEA)详解
  • 规范即代码:统一代码治理引擎canon的设计与实践
  • 微型高精度GPS模块技术解析与应用实践
  • LLM任务描述生成与分类技术解析与实践
  • TSRBENCH:多模态时间序列推理基准测试框架解析
  • 告别 User Interface:在 Xilinx UltraScale 上,用 AXI 接口玩转 DDR4 MIG IP 有多简单?
  • Delphi移动端开发避坑:TNetHTTPClient在iOS和Android上的超时设置差异详解
  • 别再死记硬背Word2vec公式了!用Python和Gensim库5分钟跑出你的第一个词向量模型
  • Java向量API配置全链路解析(从-Djdk.incubator.vector.API=enable到RuntimeFeature检测失效的底层真相)
  • 如何限制单一用户并发登录数实现互踢机制?
  • 为什么92%的Java团队在外部函数配置上多花3倍调试时间?揭秘ClassLoader隔离、动态库加载顺序与符号冲突隐性规则
  • 别再傻傻分不清了!LM358和LM324到底怎么选?从引脚图到实战应用,一次讲透
  • 从零构建高可用Agent:后端架构实战与避坑指南
  • 大模型为什么会有“幻觉”——从训练方式到推理局限
  • ARM浮点指令集架构与寄存器规范详解
  • ACMER X1三合一加工设备:激光雕刻与CNC铣削全解析
  • 视觉AI虚拟训练平台SPHINX:从原理到工业应用
  • 私有化部署ChatGPT API服务器:从原理到实战部署指南
  • 手把手教你用GLIP实现零样本目标检测:从COCO数据集加载到模型推理全流程
  • 现在不掌握低代码内核调试=主动放弃技术话语权:2024Q3主流平台(Jeecg、LowCodeEngine、AppSmith)内核调试兼容性速查表
  • SANA-Video:基于块线性扩散Transformer的高效视频生成技术
  • 自进化AI系统的社会性风险与安全防护策略
  • ai辅助钱包开发:让快马kimi生成uniswap v3流动性管理组件代码
  • 从‘抓瞎’到‘精准定位’:用Android Profiler内存分析器揪出Fragment和Activity泄漏的完整实战
  • 保姆级教程:在蓝桥杯开发板上用CX20106A超声波测距,从原理图接线到代码调试全流程
  • SQL实战:用论坛发帖表t1,5分钟搞懂UPDATE、WHERE和GROUP BY的核心用法
  • 多模态视频检索技术:从数据集构建到模型部署全解析
  • ARM嵌入式单元测试实战与Tessy框架解析
  • 用GPT-4给Syzkaller打工:手把手教你用KernelGPT自动生成Linux内核模糊测试规约
  • 2025届必备的六大降AI率网站推荐