二叉树中前序,中序,后序遍历的迭代实现

二叉树的遍历(lc)

二叉树的遍历可分为前序,中序,后序三种。其中前序最为简单,中序和后序较复杂。为什么这么说????????
可能大家会想,如果用递归的方法实现,那么不都是三四行代码吗?递归函数中一个判断当前节点为空的边界条件,然后dfs(cur->left), dfs(cur->right), print(cur) 三者交换不同的顺序,就实现了前中后序遍历。的确,这样是没错的,但面试中面试官经常会禁止我们用递归的方法实现,这时该怎么办呢?
不用递归,那么很容易想到用栈模拟递归,因为本质上递归就是通过递归栈实现的,我们只需要显示的构造出调用栈的过程即可。看起来思路很简单,但实现起来却远比这个思路复杂。
首先明确几点共识:用栈模拟递归,那么压栈操作模拟的就是进入下一层递归的过程,弹出当前栈顶的元素并进行处理模拟的就是遍历当前节点的过程。
接下来,我将依次分析前序,中序,后序的迭代遍历。

前序

前序的遍历过程是根->左->右,那么我们需要先遍历当前节点,再遍历左子节点和右子节点,初始化时可以将根节点压入栈中。由于需要先遍历当前节点,所以第一件事就是弹栈操作,并进行处理。如何解决先左后右的问题呢?很容易想到,先将右子节点压栈,再将左子节点压栈就好了。这样弹栈处理的时候自然就是先遍历左子节点,后遍历右子节点。
思路比较简单,话不多说,直接贴代码:

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ans;
        stack<TreeNode*> pre;
        if(!root) return ans;
        pre.push(root);
        while(pre.size()) {
            //弹出当前的栈顶节点,并进行处理
            TreeNode* cur = pre.top();
            pre.pop();
            ans.emplace_back(cur->val);
            //依次将右子节点和左子节点压栈,当然,若不存在则没必要压栈
            if(cur->right) pre.push(cur->right);
            if(cur->left) pre.push(cur->left);
        }
        return ans;
    }
};

中序

中序遍历的顺序是左->根->右,这个遍历方式就比较复杂了,我们需要先遍历当前节点的左子节点,直到全部遍历结束,才可以遍历当前节点,然后继续遍历右子节点。因此,前面的压栈操作是连续的,直到遇到当前节点为空,说明此时遍历到了左子树中最开始遍历的节点,然后再弹出栈顶节点,进行处理,注意为什么非要用弹出的栈顶节点来表示当前节点,而不用遍历过程中维护的指针呢?这是因为后面涉及到右子树也遍历结束后需要回溯到当前的节点,此时只能从栈顶节点中获取该节点。当前节点处理完毕后,再进入右子节点进行遍历。
代码如下:

    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ans;
        if(!root) return ans;
        stack<TreeNode*> inorder;
        TreeNode* cur = root;
        while(inorder.size() || cur) {
            //先不停的进入左子树,直到最后一个
            while(cur) {
                inorder.push(cur);
                cur = cur->left;
            }
            //当前节点的左子树遍历结束,遍历当前节点,需要注意的是当前节点应该是栈顶节点,因为遍历过的节点已经全部删除
            if(inorder.size()) {
                //遍历处理当前节点
                cur = inorder.top();
                ans.emplace_back(cur->val);
                inorder.pop();
                //遍历当前节点的右子节点
                cur = cur->right;
            }
        }
        return ans;
    }

后序

我认为后序遍历是最复杂的,在实现时遇到了较大的问题。首先说后序遍历的顺序是左->右->根,简单想想好像和中序遍历是比较类似的,都可以先将左子节点一直压栈,直到当前节点为空,然后再将指针指向右子节点,在右子树中进行遍历,最后再处理当前节点。没错,我刚开始就是这么想的,但是在自己举了几个例子以后,发现有几个问题。首先我们必须想清楚什么时候说明右子树遍历结束了呢?稍微思考一下,可能会想到右子节点为空时说明当前节点的右子树遍历肯定结束了,可问题是如果当前节点的右子节点不为空呢?
我们构造这样一种情况:当遍历完某个节点的左子树中的全部节点后,此时栈顶节点就是当前节点cur_node,此时应该做的事是根据当前栈顶节点cur_node,依次处理其右子节点(压栈操作),遍历结束后栈顶节点依然是当前节点cur_node,然后再弹出栈顶的当前节点进行处理。!我们在这里暂停一下,敏锐的人可能已经发现对右子树处理之前栈的状态和对右子树处理之后栈的状态完全一样。并且两者都是需要依据栈顶节点进行下一步操作,因此两者都会到达判断该栈顶节点cur_node的这一步。来源不同,但当前状态一致,这种情况怎么办?
一种有效的办法是用一个指针pre维护当前最后一次弹栈操作时的节点,这样我们可以利用这个指针来帮助我们区分上文中的那两种情况,具体来说,如果取出栈顶的当前节点,发现其右子节点为空或者等于pre,那么 说明其右子树已经遍历结束了,这是因为如果其右子树不为空,那么遍历过程中最后弹栈的一定是cur_node的右子节点;而如果cur_node的右子节点不为空,且不等于pre,说明其还没有遍历右子树,这时我们只需要继续遍历cur_node的右子树即可。
代码:

    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> ans;
        if(!root) return ans;
        stack<TreeNode*> back;
        TreeNode* pre = nullptr;//记录最近出栈的一个节点,用来判别当前状态是还没有遍历右子树还是已经遍历结束
        TreeNode* cur = root;//当前遍历节点
        while(cur || back.size()) {
            //首先遍历左子树
            while(cur) {
                back.push(cur);
                cur = cur->left;      
            }
            //取栈顶元素可以为了后面依次弹栈做准备
            cur = back.top();
            //若右子节点为空,或者上一次出栈的就是右节点,那么说明当前节点的子树遍历结束,则需要遍历当前节点,同时修改pre,并设置下一次需要遍历的节点
            if(!cur->right || cur->right == pre) {
                ans.emplace_back(cur->val);
                back.pop();
                pre = cur;//记录最近出栈的节点
                cur = nullptr;//方便下一次取栈顶节点
            }
            //当前节点的右子树还没有开始遍历,则需要继续遍历右子树
            else {
                cur = cur->right;
            }
        }
        return ans;
    }

总结

学习二叉树的迭代遍历过程是十分有意义的,可以帮助我们更深入的理解体会二叉树遍历,为之后二叉树遍历的各种用法提供帮助。比如二叉搜素树的中序遍历就是排序过程。很多二叉树的问题本质上都是二叉树遍历过程的变形。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值