二叉树的前中后序遍历(非递归迭代实现)

1.二叉树的前序遍历

1.1 思路分析

前序遍历的顺序是根-左子树-右子树,所以首先从根节点开始,顺着访问左子树:1、2、4。此时,还剩下节点1、节点2、节点3的右子树没有访问。后面倒着访问节点1、2、4的右子树就行了。所以非递归的前序遍历是这样处理的:把一棵二叉树分为两个部分:1、左路节点;2、左路节点的右子树。如下图所示:

对于每一棵右子树,也是同样划分为这两个部分进行处理。

如何倒着取处理左路节点的右子树?我们可以借助栈来处理左路节点的右子树。以上图中的树为例,从根节点1开始,依次访问左路节点1、2、4。在访问的过程中,将左路节点节点放到要返回的vector中,同时把左路节点放到栈里(根据栈的性质,就可以倒着往上依次访问左路节点的右子树)。如下图所示:

接着,依次取栈的栈顶元素,同时访问栈顶元素的右子树。

1、栈顶元素4出栈,再访问节点4的右子树,将节点4的右子树入栈,由于该节点的右子树为空,则结束;

2、接着,栈顶元素2出栈,再访问节点2的右子树,此时节点2的右子树(节点5)入栈,同时将节点5放入到vector中;

3、栈顶元素节点5出栈,再访问节点5的右子树,将节点5的右子树入栈,由于该节点的右子树为空,则结束;

4、栈顶元素1出栈,再访问节点1的右子树,与之前的处理一样,将节点1的右子树分为左路节点和和左路节点的右子树,依次访问节点3、6;左路节点3、节点6入栈,同时节点3、6放入vector中;

5、栈顶元素节点6出栈,再访问节点6的右子树,将节点6的右子树入栈,由于该节点的右子树为空,则结束;

6、栈顶元素节点3出栈,再访问节点3的右子树节点7,节点7入栈并放入vector中;

7、栈顶元素节点7出栈,再访问节点7的右子树,再访问节点7的右子树,将节点7的右子树入栈,由于该节点的右子树为空,则结束。

具体如下图所示:

1.2 代码实现

vector<int> preorderTraversal(TreeNode* root)
{
    vector<int> ret;
    stack<TreeNode*> st;

    TreeNode*cur=root;
    while(cur||!st.empty())
    {
        while(cur)
        {
            //将二叉树的左路节点入栈,并同时放入数组中
            st.push(cur);
            ret.push_back(cur->val);
            cur=cur->left;
        }

        //栈顶元素出栈,同时该元素的右子树节点入栈
        TreeNode* top=st.top();
        st.pop();
        cur=top->right;
     }
     return ret;
}

2.二叉树的中序遍历

2.1 思路分析

二叉树的中序遍历的思路与前序遍历的思路差不多,中序遍历还是将二叉树分为左路节点和右子树,如下图所示。

在前序遍历中,我们将左路节点入栈时,就把他们放入到要返回的vector中了,这样就符合前序遍历的顺序了。中序遍历的顺序是:左子树->根节点->右子树

1、我们将左路节点入栈,但是不放入到vector中;一直走到节点4的左子树为空,停止入栈,此时可以认为节点4的左子树是空,已经遍历过了;

2、栈中的节点依次出栈(当从栈中取出节点时,就意味着该节点的左子树已经被访问过了),第一个出栈的是栈顶元素节点4,并放入到vector中;访问完节点4的左子树和节点4本身(根节点),此时就该访问节点4的右节点,为空;

3、接着,栈顶元素节点2出栈,并放入到vector中;然后访问节点2的右子树节点5,与第一步一样,节点5入栈(由于节点5左右子树均为空,故只有节点5独自入栈);

4、栈顶元素节点5出栈,并放入到vector中;此时访问节点5的右子树,为空;

5、栈顶元素节点1出栈,并放入到vector中;然后访问节点1的右子树,与第一步一样,将节点1的右子树的左路节点3、6依次入栈;

6、栈顶元素节点6出栈,并放到vector中;访问完节点6的左子树和节点6本身(根节点),此时就该访问节点6的右节点,为空;

7、栈顶元素节点3出栈,并放到vector中;然后访问节点3的右子树,与第一步一样,节点3的右子树节点7入栈;

8、栈顶元素节点7出栈,并放到vector中;此时访问节点7的右子树,为空,则结束。

2.2 代码实现

vector<int> inorderTraversal(TreeNode* root)
{
    vector<int> ret;
    stack<TreeNode*> st;

    TreeNode*cur=root;
    while(cur||!st.empty())
    {
        while(cur)
        {
            //将二叉树的左路节点入栈
            st.push(cur);
            cur=cur->left;
        }

        //栈顶元素出栈,同时将该栈顶元素放入数组中
        TreeNode* top=st.top();
        st.pop();
        ret.push_back(top->val);
        cur=top->right;
     }
     return ret;
}

3.二叉树的后序遍历

3.1 思路分析

后续遍历的顺序是左子树->右子树->根,对于后续遍历依然是将二叉树分为左路节点和右子树,如下图。

对于栈顶元素可以像中序遍历那样直接将栈顶元素放入vector中,然后pop掉吗?答案是不可以,因为对于中序遍历,从栈中取出一个左路节点时,就意味着该节点的左子树已经被访问过了,接着就该访问根节点了,将栈顶元素放入vector中,然后pop掉就相当于访问根节点。后续遍历不能这样做,因为后续遍历要在访问完右子树之后再去访问根节点。

对于后续遍历来说,能不能直接将栈顶元素直接pop掉,然后放入到数组vector中,需要看情况。以下两种情况可以直接将栈顶元素pop掉,然后放入到数组vector中。1、栈顶元素的右子树为空;(如果栈顶元素的右子树不为空,要先访问右子树,再访问根节点。) 2、右子树已经被访问过了。那么问题来了,右子树为空很好判断,但是如何判断某个节点的右子树是否已经被访问过了呢?我们可以定义一个prev指针,每次访问完一个节点并出栈之后,将这个节点的指针赋值为prev指针,就可以通过prev来判断,该被访问过的节点是否为当前栈顶节点的右子树。

具体过程如下图所示。

3.2 代码实现

vector<int> postorderTraversal(TreeNode* root)
{
    vector<int> ret;
    stack<TreeNode*> st;
    TreeNode*prev=nullptr;

    TreeNode* cur=root;
    while(cur||!st.empty())
    {
        //遍历二叉树的左路节点并入栈
        while(cur)
        {
            st.push(cur);
            cur=cur->left;
        }

        //取到栈顶元素时,栈顶节点的左子树已经被访问过了
        TreeNode*top=st.top();

        //如果该栈顶元素的右子树为空,或者该栈顶元素的右子树已经被访问过了,
        //则访问该栈顶元素
        if(top->right==nullptr||top->right==prev)
        {
            st.pop();
            ret.push_back(top->val);
            //记录prev
            prev=top;
        }
        else
        {
            //否则就先处理右子树
            cur=top->right;
        }
    }
    return ret;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值