非递归:
提交代码:
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root)
{
vector<int> arr;
stack<TreeNode*> s;
TreeNode *p=root;
TreeNode *last=NULL;
while(p!=NULL||s.empty()!=true)
{
while(p!=NULL)
{
s.push(p);
p=p->left;
}
p=s.top();
if(p->right==NULL||p->right==last)
{
// p=s.top();这一句不能写在这里。
arr.push_back(p->val);
last=p;//记录
s.pop();
//p=s.top();//不置空可以吗。不置空会超出内存限制。你要明白如果不置空,用p=s.top()实际上进入循环体后会再次把这个p压进栈里。
p=NULL;
}
else
{
p=p->right;
}
}
return arr;
}
};
1
/ \
2 3
/ \
4 5
\
6
过程:
1,2,4入栈。【1,2,4】。此时4的左孩子为空,但是右孩子不为空,所以继续把6压进栈里,【1,2,4,6】。此时6的左右孩子都为空了。
while(p!=NULL)
{
s.push(p);
p=p->left;
}
保证了左孩子为空了。再加一个判断条件if(p->right==NULL)保证右孩子为空。左右孩子都为空了(或者说已经访问过了),则可访问根结点了。
6出栈。当前栈为【1,2,4】。
常规出栈
arr.push_back(p->val);
s.pop();
注意此时的p仍然是指向6的。所以要改,改成什么呢?改成此时的栈顶指针4吗?NO。是置空。
因为:
while(p!=NULL)
{
s.push(p);
p=p->left;
}
p=s.top();//在这里取了s的top()。栈顶指针。如果重复取的话,就会爆内存。
那如果现在是【1,2,4】。
p=s.top()之后,现在指向4了。然后呢?执行if(p->right==NULL)的话。
if(p->right==NULL)不成立。但是4的左孩子和右孩子都已经被访问过了,理应出栈。所以要增加一个判断条件。就是用一个指针记录下上一次访问的结点。
根据后序遍历:左-右-根。如果上一次访问的结点是右子树,那当前结点就出栈。
所以在6出栈之前,先把结点6的指针记录下来。
last=p;//记录
s.pop();
其实还不算理解得特别深刻。只是看了别人的代码和说明。对于具体为什么要这么做,还有待进一步的了解。其实是自己根据别人发现的问题去解决问题。但是自己却没有从无到有地自己去发现问题。嗯,继续加油。毕竟后序遍历的非递归挺难理解的。
关键点大概是,不知道右子树到底访问了没吧?只有左右子树都访问了,才能访问根结点。
联系先序遍历,根-左-右。中序遍历,左-根-右。
先序:
TreeNode* p=root;
while(p!=NULL||s.empty()!=true)
{
while(p!=NULL)
{
s.push(p);
arr.push_back(p->val);
p=p->left;//访问左子树
}
if(s.empty()!=true)//其实这个判断条件我也是看别人的代码看来的。在后序遍历中我以为还要用这个条件。为什么不用了呢?有待思考。
{
p=s.top();
s.pop();
p=p->right; //访问右子树。只是空子树也照样可以访问吧。所以加不加判断是否为空都可以。
}
}