给定一个二叉树,返回它的 后序 遍历。

示例:

输入: [1,null,2,3]
1
\
2
/
3 输出: [3,2,1]

思路:一开始编写二叉树后序遍历的程序,感觉定级为困难有点欠妥,确实,如果用递归的做法来做,和前序中序没有太大的程序上的变动,但是如果用非递归的做法来做,就会发现确实要多了一个判断过程。

(1)递归

    vector<int> a;
vector<int> postorderTraversal(TreeNode* root) {
if(root)
{
postorderTraversal(root->left);
postorderTraversal(root->right);
a.push_back(root->val);
}
return a;
}

(2)非递归

后序遍历的非递归实现是三种遍历方式中最难的一种。因为在后序遍历中,要保证左孩子和右孩子都已被访问并且左孩子在右孩子前访问才能访问根结点,这就为流程的控制带来了难题。下面介绍两种思路。

第一种思路:对于任一结点P,将其入栈,然后沿其左子树一直往下搜索,直到搜索到没有左孩子的结点,此时该结点出现在栈顶,但是此时不能将其出栈并访问,因此其右孩子还没有被访问。所以接下来按照相同的规则对其右子树进行相同的处理,当访问完其右孩子时,该结点又出现在栈顶,此时可以将其出栈并访问。这样就保证了正确的访问顺序。可以看出,在这个过程中,每个结点都两次出现在栈顶,只有在第二次出现在栈顶时,才能访问它。因此需要多设置一个变量标识该结点是否是第一次出现在栈顶。

  struct BTNode
 {
TreeNode* node;
bool isFirst;
BTNode(TreeNode* p): node(p),isFirst(true){}
};
vector<int> postorderTraversal(TreeNode* root) {
vector<int> a;
stack<BTNode*> s;
TreeNode* p=root;
BTNode*temp;
while(p!=NULL || !s.empty())
{
while(p)
{
BTNode* b=new BTNode(p);
s.push(b);
p=p->left;
}
if(!s.empty())
{
temp=s.top();
s.pop();
if(temp->isFirst==true)
{
temp->isFirst=false;
s.push(temp);
p=temp->node->right;
}
else
{
a.push_back(temp->node->val);
p=NULL;
}
}
}
return a;
}

这里多定义了一个结构体,里面包含了一个标志位isFirst,用来判断这个根节点是不是第一次来到栈顶,如果是第一次,isFirst==true,我们需要继续遍历此节点的右子树,并将其置位为false,这个时候要注意,前面已经将此节点pop出来了,要再次将其push到栈中,如果是第二次,就证明它的左右子树都已经遍历过了,所以就直接将此节点的值打印就好了。

第二种思路:要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,则可以直接访问它;或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。

 vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> s;
TreeNode *cur=NULL; //当前结点
TreeNode *pre=NULL; //前一次访问的结点
vector<int> a;
s.push(root);
while(root && !s.empty())
{
cur=s.top();
if((cur->left==NULL&&cur->right==NULL)||(pre!=NULL &&(pre==cur->left||pre==cur->right)))
{
a.push_back(cur->val); //如果当前结点没有孩子结点或者孩子节点都已被访问过
s.pop();
pre=cur;
}
else
{
if(cur->right!=NULL)
s.push(cur->right);
if(cur->left!=NULL)
s.push(cur->left);
}
}
return a;
}

这个思路很好,目的也是要保证遍历过程的正确性,多使用了一个指针pre来存储前一次访问的节点,这样就可以判断此节点的右子树有没有被访问过。判断条件中

(pre!=NULL &&(pre==cur->left||pre==cur->right))

很难理解,一开始会想,这里的pre==cur->left是错误的,因为如果我前一个访问的是该节点的左孩子,那就可以直接访问该节点吗,怎么可能呢。仔细想,就是这样的。因为这种情况只可能出现在,该节点没有右孩子,所以上一个访问完左孩子,直接就可以访问该节点。如果有右孩子在,上一个访问的节点不可能是左孩子,因为右孩子是在此节点之后打入栈中的,会更早的出现在栈顶。

												

Leetcode(145)-二叉树的后序遍历的更多相关文章

  1. LeetCode 145. 二叉树的后序遍历(Binary Tree Postorder Traversal)

    145. 二叉树的后序遍历 145. Binary Tree Postorder Traversal 题目描述 给定一个二叉树,返回它的 后序 遍历. LeetCode145. Binary Tree ...

  2. Java实现 LeetCode 145 二叉树的后序遍历

    145. 二叉树的后序遍历 给定一个二叉树,返回它的 后序 遍历. 示例: 输入: [1,null,2,3] 1 \ 2 / 3 输出: [3,2,1] 进阶: 递归算法很简单,你可以通过迭代算法完成 ...

  3. LeetCode 145 二叉树的后序遍历(非递归)

    题目: 给定一个二叉树,返回它的 后序 遍历. 示例: 输入: [1,null,2,3] 1 \ 2 / 3 输出: [3,2,1] 进阶: 递归算法很简单,你可以通过迭代算法完成吗? 解题思路: 1 ...

  4. 【leetcode 145. 二叉树的后序遍历】解题报告

    前往二叉树的:前序,中序,后序 遍历算法 方法一:递归 vector<int> res; vector<int> postorderTraversal(TreeNode* ro ...

  5. Leetcode 145. 二叉树的后序遍历

    题目链接 https://leetcode-cn.com/problems/binary-tree-postorder-traversal/description/ 题目描述 给定一个二叉树,返回它的 ...

  6. LeetCode 145. 二叉树的后序遍历(Binary Tree Postorder Traversal)

    题目描述 给定一个二叉树,返回它的 后序 遍历. 示例: 输入: [1,null,2,3] 1 \ 2 / 3 输出: [3,2,1] 进阶: 递归算法很简单,你可以通过迭代算法完成吗? 解题思路 后 ...

  7. LeetCode 145. 二叉树的后序遍历 (用栈实现后序遍历二叉树的非递归算法)

    题目链接:https://leetcode-cn.com/problems/binary-tree-postorder-traversal/ 给定一个二叉树,返回它的 后序 遍历. 示例: 输入: [ ...

  8. LeetCode 145 ——二叉树的后序遍历

    1. 题目 2. 解答 2.1. 递归法 定义一个存放树中数据的向量 data,从根节点开始,如果节点不为空,那么 递归得到其左子树的数据向量 temp,将 temp 合并到 data 中去 递归得到 ...

  9. LeetCode:二叉树的后序遍历【145】

    LeetCode:二叉树的后序遍历[145] 题目描述 给定一个二叉树,返回它的 后序 遍历. 示例: 输入: [1,null,2,3] 1 \ 2 / 3 输出: [3,2,1] 进阶: 递归算法很 ...

  10. 【LeetCode】145. 二叉树的后序遍历

    145. 二叉树的后序遍历 知识点:二叉树:递归:Morris遍历 题目描述 给定一个二叉树的根节点 root ,返回它的 后序 遍历. 示例 输入: [1,null,2,3] 1 \ 2 / 3 输 ...

随机推荐

  1. 利用sklearn进行字典&文本的特征提取

    写在前面 这篇博客主要内容: 应用DictVectorizer实现对类别特征进行数值化.离散化 应用CountVectorizer实现对文本特征进行数值化 特征提取API sklearn.featur ...

  2. JVM虚拟机基础

    JVM 全称Java Virtual Machine,也就是我们耳熟能详的Java 虚拟机.它能识别.class 后缀的文件,并且能够解析它的指令,最终调用操作系统上的函数,完成我们想要的操作. Ja ...

  3. MySQL 中的临时表

    在使用 explain 解析一个 sql 时,有时我们会发现在 extra 列上显示 using temporary ,这表示这条语句用到了临时表,那么临时表究竟是什么?它又会对 sql 的性能产生什 ...

  4. JavaScript常用API

    JavaScript常用API 节点属性 文档节点 事件监听.一出事件 获取元素方法

  5. 新编日语1234册/重排本/全册 pdf

    网上找的资源链接大部分都失效了,无奈之下只好淘宝购买.顺便分享一下吧. 链接: https://pan.baidu.com/s/1v5-osHKrIPzlgpd8yNIP5Q 提取码: kexn

  6. CSRF Cross-site request forgery 跨站请求伪造

    跨站请求伪造目标站---无知用户---恶意站 http://fallensnow-jack.blogspot.com/2011/08/webgoat-csrf.html https://wiki.ca ...

  7. SpringCloud配置刷新机制的简单分析[nacos为例子]

    SpringCloud Nacos 本文主要分为SpringCloud Nacos的设计思路 简单分析一下触发刷新事件后发生的过程以及一些踩坑经验 org.springframework.cloud. ...

  8. Elasticsearch从0到千万级数据查询实践(非转载)

    1.es简介 1.1 起源 https://www.elastic.co/cn/what-is/elasticsearch,es的起源,是因为程序员Shay Banon在使用Apache Lucene ...

  9. slowhttptest慢速攻击工具使用详解

    参考文章 浅谈"慢速HTTP攻击Slow HTTP Attack" HTTP慢速攻击 Slowhttptest攻击原理 InstallationAndUsage tag: #slo ...

  10. Spark 应用监控告警-Graphite_exporter

    Spark 应用监控告警-Graphite_exporter Spark监控和工具 Web界面 事后查看 REST API 度量 高级工具 一.下载graphite_exporter 1.1 修改gr ...