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

示例:

输入: [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是错误的,因为如果我前一个访问的是该节点的左孩子,那就可以直接访问该节点吗,怎么可能呢。仔细想,就是这样的。因为这种情况只可能出现在,该节点没有右孩子,所以上一个访问完左孩子,直接就可以访问该节点。如果有右孩子在,上一个访问的节点不可能是左孩子,因为右孩子是在此节点之后打入栈中的,会更早的出现在栈顶。


posted on 2018-05-24 17:10  Mini_Coconut  阅读(194)  评论(0编辑  收藏  举报