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

三次到达:理解二叉树非递归遍历

分析二叉树递归实现

在这里插入图片描述

仔细分析二叉树的遍历,可以得到结论:

  • 遍历的路线是相同的
  • 不同的遍历结果取决与访问的时机
    • 前序:第一次到达该节点访问
    • 中序:第二次到达该节点访问
    • 后续:第三次到达该节点访问

有了上面这个结论,其实就可以想办法去模拟这个遍历路线

路线分析

在这里插入图片描述

可以发现一个规律:

  • 针对一颗二叉树,遍历路线是:一直走左边的节点,左边节点走完了,此时再走最后一个左节点的右子树
  • 在处理右子树时还是依照上面的规律
    在这里插入图片描述

模拟路线

模拟路线需要两个工具:

  1. cur (探路先锋):表示“我当前正要处理的节点”
  2. st (记忆栈):模拟“函数调用栈”,帮我们“记住”回家的路(父节点)

依照上面的规律可以实现一个框架代码

while (cur || !st.empty()) {// 1. “一直走左边的节点”while (cur) {// ... (这是“第1次到达”,先别急着访问)st.push(cur); // 记住“回家的路”cur = cur->left; // 一直向左!}// 2. “左边节点走完了”(cur == null 了)//    我们从“记忆栈”里掏出“最后一个左节点”TreeNode* top = st.pop();// 3. “此时再走...右子树”//    我们准备去“top”的右子树探险cur = top->right; // 4. “在处理右子树时还是依照上面的规律”//    (因为 cur = top->right, 循环回到顶部,//     又开始对这个“右子树”执行“一直走左边”)
}

前序遍历 (第 1 次到达时访问)

  • 时机: “第一次到达”,也就是我们刚遇到这个节点,准备“一直向左”之前
  • 代码:
vector<int> preorderTraversal(TreeNode* root) {vector<int> ret;stack<TreeNode*> st;TreeNode* cur = root;while(cur || !st.empty()) {// “一直走左边的节点”while(cur) {// >> 时机 1 <<// 第一次到达!立刻访问!ret.push_back(cur->val); st.push(cur); // 记住回家的路cur = cur->left; // 走左}// “左边走完了”TreeNode* top = st.top();st.pop();// “去右边”cur = top->right;}return ret;
}

中序遍历 (第 2 次到达时访问)

  • 时机: “第二次到达”,也就是“左边走完了 (cur == null)”,我们从“记忆栈”里 pop 出它的时候
  • 代码:
vector<int> inorderTraversal(TreeNode* root) {vector<int> ret;stack<TreeNode*> st;TreeNode* cur = root;while(cur || !st.empty()) {// “一直走左边的节点”while(cur) {st.push(cur);cur = cur->left;}// “左边走完了”(cur == null)TreeNode* top = st.top(); // pop!st.pop();// >> 时机 2 <<// 从“左边”回来了!立刻访问!ret.push_back(top->val);// “去右边”cur = top->right;}return ret;
}

后序遍历 (第 3 次到达时访问)

  • 时机: “第三次到达”,也就是从“右子树”也回来之后
  • 挑战: 当我们 pop(top) 时,我们只知道是“第 2 次到达”(从左边回来)。我们必须先去 top->right。只有当 top->right 也处理完了,我们才能访问 top
  • 怎么办?
    1. 我们 pop 之前,先 top() “偷看一下”。
    2. top 就是我们“第 2 次”到达的节点。
    3. 我们问:它的“右子树” (top->right) 处理完了吗?
      • A: 如果 top->right == nullptr (没右子树),那“第 3 次”和“第 2 次”是同时的。可以访问!
      • B: 如果 top->right 刚被处理过,那也可以访问!
    4. 如何知道“刚被处理过”?我们引入一个 prev 指针,记录“上一个被访问的节点”。如果 top->right == prev,就说明“右边”刚回来!
  • 代码:
vector<int> postorderTraversal(TreeNode* root) { vector<int> ret;stack<TreeNode*> st;TreeNode* cur = root;TreeNode* prev = nullptr; // 记录“上一个被访问的节点”while(cur || !st.empty()) {// “一直走左边的节点”while(cur) {st.push(cur);cur = cur->left;}// “左边走完了”(cur == null)// 先不 pop!先“偷看”!TreeNode* top = st.top();// >> 时机 2 (决策点) <<// 我们在“第 2 次”到达 top// 我们要判断“右边”去过了吗?if (top->right == nullptr || top->right == prev) {// A: 没右子树 (第 2、3 次同时)// B: 右子树刚回来 (这就是第 3 次)// >> 时机 3 <<// 访问!ret.push_back(top->val);st.pop(); // 真正“出栈”prev = top; // 记录“我刚访问了 top”// cur 保持为 null,以便下一轮继续“pop”}else {// “右边”还没去过!// 必须先去“右边”!cur = top->right;}}return ret;
}

总结:真正需要记住的

只需要“理解”这**“一套理论”**:

  1. 路线模拟:我们用 cur(探路)和 st(记忆)来模拟这个路线

  2. 代码框架cur == nullptr && st.empty()为结束条件,然后一直往左走,最后再转向右

  3. 时机安插

    • 前序push 时访问
    • 中序pop 时访问
      要“理解”这**“一套理论”**:
  4. 路线模拟:我们用 cur(探路)和 st(记忆)来模拟这个路线

  5. 代码框架cur == nullptr && st.empty()为结束条件,然后一直往左走,最后再转向右

  6. 时机安插

    • 前序push 时访问
    • 中序pop 时访问
    • 后序pop 前“偷看”,用 prev 判断“右边”是否回来,再访问
http://www.dtcms.com/a/499073.html

相关文章:

  • AI驱动的专业报告撰写:从信息整合到洞察生成的全新范式
  • JVM(Java虚拟机)~
  • 南充网站建设略奥网络地宝网招聘信息网
  • mmdetection训练 测试步骤
  • 【基础算法】01BFS
  • Openharmony应用开发之Ability异常退出与UIAbility数据备份开发实战
  • 我做了一个免费的 DeepResearch 网站,让科研变得更简单
  • 顺德大良那里做网站好福建省城乡和住房建设厅网站
  • 前端+AI:CSS3(二)
  • go邮件发送——附件与图片显示
  • AI+若依框架(基础篇)
  • 机器学习(2) 线性回归和代价函数
  • 基于Ollama和sentence-transformers,通过RAG实现问答式定制化回复
  • Sentinel:阿里云高并发流量控制
  • 从技术到商业:电商返利平台的核心指标设计(GMV、佣金率、留存率)与技术支撑体系
  • ShardingSphere 源码解析之分片引擎(下)
  • winxp下做网站网店设计美工培训
  • 【论文学习】大语言模型(LLM)论文
  • 做网站ssl证书必须要吗如何黑掉jsp做的网站
  • CLICKHOUSE分布式表初体验
  • 学习周报十八
  • 《Kubernetes 集群搭建全指南:从核心概念到环境部署!》
  • 找工作哪个网站好58同城做网站最简单的
  • 携程网站用js怎么做淄博哪家网络公司做网站好
  • Linux Shell 正则表达式中的 POSIX 字符集:用法与实战
  • MQTT协议,EMQX自建服务器
  • 力扣1287. 有序数组中出现次数超过25%的元素
  • Linux网络与路由配置完全指南
  • 【高并发服务器】六、日志宏的实现
  • 什么是网络割接