这是leetcode上的3个题目,要求用非递归实现,其中以后序遍历实现最难,既然递归实现的三种遍历程序只需要改变输入代码顺序,为什么循环不可以呢,带着这种执拗的想法,我开始了这次研究

我依然是将递归用栈来实现,而不打算使用改变二叉树结构的方法,那个我打算日后研究

首先以前序遍历为例

递归实现是:

void preorderTraversal(TreeNode* root) {if (root == nullptr)return;cout << root->val;preorderTraversal(root->left);preorderTraversal(root->right);
}

利用循环和栈来实现递归

我的思路是每次循环对应一次函数调用,每次函数调用的root加入栈中,思考后,写出下面的程序

void preorderTraversal(TreeNode* root) {if (root == nullptr)return;stack<TreeNode*> sta;sta.push(root);while (!sta.empty()) {root = sta.top();cout << root->val;if (root->left != nullptr) {sta.push(root->left);continue;}if (root->right != nullptr) {sta.push(root->right);continue;}sta.pop();}
}

但是经测试发现,这段程序有个巨大的漏洞,以至于无法结束循环,问题出现在pop某节点后,返回到父节点,会再次将该节点push到栈中,从而无限循环

对照递归的实现,脑补计算机函数进栈出栈的抽象图,发现计算机的函数堆栈绝非是保存root的栈可以替代的,它还保存了代码的执行位置,也就是栈指针和帧指针

我们能否用变量保存上次循环执行的代码位置呢,我们可以设置一些tag标记来模拟栈指针。但是,这里还有更好的实现方法。

我们可以保存上次循环结束时的root节点,其名lastRoot,

如果lastRoot = root->left,则说明root->left已经遍历过一遍,不用再将其加入栈中,也就是下面的代码段不用再执行

if (root->left != nullptr) {sta.push(root->left);continue;
}

如果lastRoot = root->right,则说明root->left和root->right都已经遍历过一遍,可以直接将root出栈,下面的代码段不用执行

if (root->left != nullptr) {sta.push(root->left);continue;
}
if (root->right != nullptr) {sta.push(root->right);continue;
}

理解之后,不难写出代码,前序,中序,后续遍历的区别在于改变输出当前节点代码段的位置,同递归实现一样,只是顺序的区别,程序员就是这样懒,妄想一招鲜吃遍天下

// 前序遍历
vector<int> preorderTraversal(TreeNode* root) {vector<int> out;if (root == nullptr)return out;stack<TreeNode*> sta;sta.push(root);TreeNode* lastRoot = root;while (!sta.empty()){    root = sta.top();if(lastRoot != root->right){if (lastRoot != root->left) {out.push_back(root->val);if (root->left != nullptr) {sta.push(root->left);continue;}}            if (root->right != nullptr) {sta.push(root->right);continue;}}lastRoot = root;sta.pop();}return out;
}// 中序遍历
vector<int> inorderTraversal(TreeNode* root) {vector<int> out;if (root == nullptr)return out;stack<TreeNode*> sta;sta.push(root);TreeNode* lastRoot = root;while (!sta.empty()){root = sta.top();if (lastRoot != root->right){if (lastRoot != root->left) {                if (root->left != nullptr) {sta.push(root->left);continue;}}out.push_back(root->val);if (root->right != nullptr) {sta.push(root->right);continue;}}lastRoot = root;sta.pop();}return out;
}// 后序遍历
vector<int> postorderTraversal(TreeNode* root) {vector<int> out;if (root == nullptr)return out;stack<TreeNode*> sta;sta.push(root);TreeNode* lastRoot = root;while (!sta.empty()){root = sta.top();if (lastRoot != root->right){if (lastRoot != root->left) {if (root->left != nullptr) {sta.push(root->left);continue;}}            if (root->right != nullptr) {sta.push(root->right);continue;}            }out.push_back(root->val);lastRoot = root;sta.pop();}return out;
}

另外还有其他实现方法,比如前序遍历,网上比较流行的方法是下面这种,这两种写法的思路是一样的,不过下面的要更简洁一些,虽然这种思路一开始我有些难以接受

不过还是要总结一下

1.选择一个root节点

2.将其左节点依次加入栈中

3.输出栈顶节点,弹出,然后将root设置为其右节点,重复1步骤

上面这些仅仅是程序的说明步骤,不能算是思路吧。不过要认真想一下的话,也许是跟人脑中遍历树的方式差不多,这里不做深入探讨。觉得麻烦的话,可以直接背上面总结的步骤。

vector<int> preorderTraversal(TreeNode* root) {stack<TreeNode*> sta;vector<int> out;while (root || !sta.empty()){while (root){sta.push(root);out.push_back(root->val);root = root->left;}if (!sta.empty()){root = sta.top();sta.pop();root = root->right;}}return out;
}
// 另一种写法
vector<int> preorderTraversal(TreeNode* root) {stack<TreeNode*> sta;vector<int> out;while (root || !sta.empty()){if(root){sta.push(root);out.push_back(root->val);root = root->left;}else{root = sta.top();sta.pop();root = root->right;}}return out;
}

另外一种实现思路,这次栈中仅仅保存右节点,因为是前序遍历,左节点可以直接输出,注意,这种方法仅能用于前序遍历

vector<int> preorderTraversal(TreeNode* root) {vector<int> out;if (root == nullptr)return out;stack<TreeNode*> sta;    sta.push(root);while (!sta.empty()){out.push_back(root->val);if (root->right)sta.push(root->right);if (root->left)root = root->left;else{root = sta.top();sta.pop();}}return out;
}

中序遍历其他方法(同前序遍历):

vector<int> inorderTraversal(TreeNode* root) {stack<TreeNode*> sta;vector<int> out;TreeNode* cur = root;while (cur != nullptr || !sta.empty()){if (cur != nullptr){sta.push(cur);cur = cur->left;}else{cur = sta.top();sta.pop();out.push_back(cur->val);cur = cur->right;}}return out;
}

后序遍历只能使用保存上次root节点的或tag标记的方法

后记:算法的研究深不见底,你总能从中发现新的东西,越是研究的深入,就越会发现我们平时习惯了的人类思维方式是多么神奇!

转载于:https://www.cnblogs.com/sdlwlxf/p/5054550.html

二叉树的前序、中序、后序遍历非递归实现相关推荐

  1. java中二叉树_Java工程师面试1000题224-递归非递归实现二叉树前、中、后序遍历...

    224.使用递归和非递归实现二叉树的前.中.后序遍历 使用递归来实现二叉树的前.中.后序遍历比较简单,直接给出代码,我们重点讨论非递归的实现. class Node { public int valu ...

  2. C++实现二叉树 前、中、后序遍历(递归与非递归)非递归实现过程最简洁版本

    本文并非我所写,是复制的该链接中的内容: 最近学习二叉树,想编程实现递归和非递归的实现方式: 递归的方式就不说了,因为大家的递归程序都一样:但是对于非递归的实现方式, 根据这几天的查阅资料已看到差不多 ...

  3. 【LeetCode | 二叉树前、中、后序遍历{迭代法}实现】

    1.前序遍历 // 解题思路:利用栈的原理实现以迭代方法来前序遍历(根左右)二叉树 class Solution { public:vector<int> preorderTraversa ...

  4. java数据结构学习笔记-二叉树前、中、后序遍历

    public class BinaryTreeDemo {public static void main(String args[]){Employee emp1= new Employee(1,&q ...

  5. 【LeetCode | 二叉树前、中、后序遍历{递归法}实现】

    1.前序遍历 #include <iostream> #include <vector> #include <queue> #include <algorit ...

  6. 二叉树前、中、后序线索化及遍历

    public class ThreadedBinaryTree {public static void main(String[] args){Heronodes node1=new Heronode ...

  7. 二叉树的前、中、后、层次非递归遍历(js)

    有如下二叉树  遍历: // 前序遍历, head-left-rightfunction HLR (tree) {const stack = [], res = []if (tree) stack.p ...

  8. 二叉树前序、中序、后序遍历非递归写法的透彻解析

    前言 在前两篇文章二叉树和二叉搜索树中已经涉及到了二叉树的三种遍历.递归写法,只要理解思想,几行代码.可是非递归写法却很不容易.这里特地总结下,透彻解析它们的非递归写法.其中,中序遍历的非递归写法最简 ...

  9. 二叉树的前序中序后序遍历

    二叉树的前序中序后序遍历 二叉树的遍历 前序遍历 中序遍历 后序遍历 总结 二叉树的遍历 二叉树的遍历有前序遍历,中序遍历,后序遍历三种. 今天我把二叉树的遍历方法给大家总结一下,也算对我自己学习的一 ...

  10. 【二叉树Java】二叉树遍历前序中序后序遍历的非递归写法

    本文主要介绍二叉树前序中序后序遍历的非递归写法 在探讨如何写出二叉树的前序中序后序遍历代码之前,我们先来明确一个问题,前序中序后序遍历根据什么区分? 二叉树的前序中序后序遍历,是相较根节点说的.最先遍 ...

最新文章

  1. 计算机导论第一章试题及答案,计算机导论第一章试题
  2. TSM12M触摸芯片注意事项
  3. list_for_each()与list_for_each_safe()
  4. (17)System Verilog枚举类型enum详解
  5. Python 并发部分的面试题
  6. 在静态SQL中实现动态WHERE子句
  7. C#编程总结(十一)数字证书
  8. selenium3降级到selenium2详细步骤
  9. Keil下载代码闪退的问题(已解决)
  10. oracle dataaccess component,【Delphi】运用Oracle Data Access Component(ODAC)组件
  11. 【第2重磅】王者荣耀「绝悟」升级,全英雄池解禁
  12. 28-Docker-常用命令详解-docker save/load/import
  13. 风控决策引擎——决策流路径规划
  14. 利用代理IP爬取京东网站手机数据
  15. PDF修改目录和跳转
  16. 在微信开发者工具导入整个weui的实例,查看weui的用法
  17. BZOJ 1412: [ZJOI2009]狼和羊的故事
  18. Jmeter之dubbo接口性能测试
  19. C# vb .NET读取识别条形码线性条码code128
  20. sql数据库服务器重装,保留数据重装后sqlserver数据库不能启动(sql2008,sql2012)

热门文章

  1. [转载] 钢铁是怎样炼成的——第一部第二章
  2. markdown 图片居中_Markdown更改字体、颜色、大小,插入表格等方法
  3. 买电脑怎么看配置_电脑配置怎么选?
  4. python垂直输出hello_python - 我如何获得直方图以垂直输出数据? - SO中文参考 - www.soinside.com...
  5. spring boot2 修改默认json解析器Jackson为fastjson
  6. 单片机的就业方向是什么,搞单片机是青春饭吗?
  7. 关于加载Fashion MNIST数据集时可能会出现的问题
  8. 大数据精准投放平台_大数据库(可视化精准平台,能够使您的广告更加精准有效)...
  9. powerbi输入数据_Power BI 的多种共享方式
  10. 线段树 (经典题目合集)