题目描述
今天轮到后序遍历啦。(可恶的格式:)
递归解法
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> nodes;
if(root != NULL){
vector<int> left_tree = postorderTraversal(root->left);
for(int i=0; i<left_tree.size(); i++){
nodes.push_back(left_tree[i]);
}
vector<int> right_tree = postorderTraversal(root->right);
for(int i=0; i<right_tree.size(); i++){
nodes.push_back(right_tree[i]);
}
nodes.push_back(root->val);
}
return nodes;
}
};
非递归写法
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> nodes;
stack<TreeNode*> st;
TreeNode* T = root;
TreeNode* last = NULL;
while(T){
st.push(T);
T = T->left;
}
while(!st.empty()){
T = st.top();
st.pop();
if(T->right == NULL || last == T->right){
nodes.push_back(T->val);
last = T;
}
else{
st.push(T);
T = T->right;
while(T){
st.push(T);
T = T->left;
}
}
}
return nodes;
}
};
总结
非递归写法中,后序遍历是最难的一种,原因在于对待根节点(此处根节点是广义的,任何一个节点都可以认为是其子树的根节点,下同)的态度发生了变化。
在先序遍历和中序遍历中,碰到根节点直接访问即可,因为根节点并不是最后一个要被访问的。在非递归遍历中遇到根节点有两种情况:
- 由其父节点指向而得到(T = T->left; T为新的根节点);
- 在回溯的过程中通过出栈而得到(T = st.top(); T为之前保存在栈中的路径上的节点)
对第一种情况,后序遍历时,要一直追踪到左子树的左子树的左子树....也即左下角的节点(这一点跟中序遍历一样,先序遍历时先访问再追踪);
对第二种情况,后序遍历时,并不能马上访问根节点,而是要判断其右子树是否已经被访问过或者为空,满足二者之一才能访问,不然就得先去乖乖访问右子树;所以,需要记录上一次访问的节点是什么。体现在代码上,与之前的套路也有明显的不同。
======================================================
画外音:)
非递归写法我写了好久,最开始遇到的问题就是我还按照先序和中序的框架去判断,肯定是不对的。先序和中序,栈非空的判断和左子树的追踪是合二为一的,而后序要分开。
- 经常超时,原因是,访问完根节点后又把它压到栈里去了导致陷入死循环,这说明弹栈那段代码应该放在前面了,不能像先序和中序一样放在else条件里。
2. if那里,我最开始用的条件是(last == T->right || (last == T->left && T->right == NULL)).这样是不对的,相当于把条件加强了,last跟你此时此刻的T可能都不在一个分叉上了,凭什么认为上次访问的就是T的左子树?
3. while条件里面,只能判断栈空,若加上T非空指针的判断,则会导致栈空之后仍然会进入循环,导致栈中没有东西可弹而报错。为什么先序和中序可以那样写?因为它们俩栈空和T为空一定可以同时满足,而对于后序,最后栈空是因为把(真正的)根节点弹了出去,T一定非空,所以不同时满足,又导致死循环。仔细想想。