二叉树深度遍历 python 二叉树深度遍历非递归_入栈

 如上图二叉树的遍历主要思想就是通过递归方式进行遍历,同时如果要非递归遍历的话,一般情况下,深度优先遍历需要借助stack保存中间变量的方式进行遍历,广度优先遍历的话需要借助queue来保存每一层变量的方式进行遍历。

对于深度优先遍历的递归的三种形式,不进行介绍,广度优先遍历,递归和非递归这篇文章也不进行介绍,这里就是想深刻的说下,深度优先遍历的三种非递归实现的原理。

先说下三种遍历的实现顺序吧

前序遍历:根,左,右

中序遍历:左,根,右

后续遍历:左,右,根

因为二叉树给到我们的是根结点:

所以我们应该先入栈这个根结点,然后可以入右,也可以入左,但是我们发现这个前序遍历是根左右,所以我们入左,然后入右,但是要注意这个入左,是一路进行递归的,直到左没有了,才会返回,然后进行入它上一级的右。而这个树的前序遍历也是,这样的,是根,然后访问左,然后访问左的左,也就是把整个树,理解成多个二叉树。下面画一个简单的图示。

二叉树深度遍历 python 二叉树深度遍历非递归_非递归_02

 就是有这样一颗树,然后,如何进行对树入栈,出栈,我们给出示意图:

这里我们要明确一点,就是我们用栈来实现的遍历的过程,入栈和出栈是同时进行的,也就是说这个不是先都入完栈了,我们才进行出栈,这个对于初学者来说往往是一个比较大的误会。

所以我想用示意图,能够清晰的描述出这个入栈出栈的过程。我们把入栈的过程和出栈的过程先混合画,然后再分开来画。

二叉树深度遍历 python 二叉树深度遍历非递归_二叉树深度遍历 python_03

我们按照先序的形式进行入栈,根 左 右

然后出栈顺序必然是 中序,左 根 右

上面的示意图,已经能够清晰的看到。

代码如下:我们想先序遍历,则是入栈前进行放在vector中,然后中序遍历就是出栈的时候放在       vector中。

/*思路
1、借用stack
2、在压最左边栈的同时需要把其节点也输出。
3、然后再把指针指向右边节点,然后在循环。
*/
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        if(!root){
            return result;
        }
        stack<TreeNode*> s;
        TreeNode *cur = root;
        while(!s.empty()|| cur){
            while(cur){
                result.push_back(cur->val);
                s.push(cur);
                cur = cur->left;
            }
            /*退出则说明左边已经放完了,此时需要弹出栈顶元素,同时查看他的右节点,然后在继续循环*/
            cur = s.top();
            s.pop();
            cur = cur->right;
        }

        return result;
    }

中序非递归遍历的代码如下所示:

/*思路:
1、用栈的方式来处理,不用递归。
2、栈的思想就是,不需要提前把root节点压入栈,而是在循环中才压入。同时因为是中序遍历,所以最先压栈的是按理说是右,再是root,再是左。但是这里在循环过程中,需要从root开始判断压入栈的左子树是否为空,不为空则一直把左边节点压入栈,也就是按照树的最左边从root到业务节点压入栈。此时叶子节点左子树为空,则此时弹出stack top元素,写入result数组中,此时弹出的元素就是最左边的叶子节点数据。然后此时需要拿着这个节点看看他的右节点。如果不为空,则需要把他的右节点压栈,并且指针指向他右节点的左孩子。如果右节点为空,则继续弹出stack top元素,写入result中,此时弹出的则是数的左边那条线倒数第二个节点。如果右节点存在,则将右节点压入栈,同时下一个指针指向来右节点的左节点,如果左节点为空,则弹出stack top元素(此时弹出的就是这个右节点)。指针就系指向p->right。此时为空则继续弹出stack。。。。循环继续。直到stack为空&&p指针同时为空,则说明占用没有来数据同时把树已经遍历完成(p==nulptr)
*/
/*思路:非递归。
1、用一个栈,但是不会想广度优先算法一样先把root放在queue中
2、把左子树一直放在stack中,直到坐左边的节点
3、然后为空周,再把右子树放进去,循环判断。*/
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode *> s;
        if(!root){
            return result;
        }
        TreeNode *p = root;
        /*注意里面不需要临时节点指针了。直接用p就行了。*/
        while(!s.empty()||p){

            /*这里是把最左边全部压入栈,直到最左边的叶子节点*/
            while(p){
                s.push(p);
                p = p->left;
            }

            /*把左边全部压入栈,直到最左边的节点压完,此时弹出坐左边节点。*/
            p = s.top();
            s.pop();
            result.push_back(p->val);
            /*再看看这个最左边节点的右节点是否为空
            为空,继续弹出剩下的最左边,否则将右节点对应的最左边全部压入栈,然后在重复上面的循环*/
            p = p->right;
        }
        return result;
    }

对于后序遍历的非递归:

我们知道我们在入栈的时候是先序,那么出栈的时候,正好是中序。

那么我们想让后序遍历的话,我们可以从两个方向上下手。

因为现在入栈的顺序是 根 左 右(先序遍历),然后出栈的顺序,自然就是,左 右 根(中序遍历),而如果我们入栈的顺序,是 根 右 左,那么出栈的顺序自然是 右 左  根,但是这个右 左 根不是一种遍历形式,所以我们不会进行使用。

但是这里给了我们一种提示,如果我们把。 根 右 左,这种入栈形式保留在vector中,那么把这个保留的vector直接反转不就是我们的后序遍历非递归形式吗?   根 右 左  ------左 右 根

而这个根 右 左  出栈的顺序是右 左 根,是不符合要求的,但是我们对其进行操作,是不是也可以变成后序遍历的非递归形式呢?答案是可以的,下面分别从刚才分析的两个角度来解决问题。

1.从入栈的角度来分析,我们把入栈顺序改变,改为根,右,左

代码实现如下:

/*思路:
1、非递归方式通过stack,从右边一次放入stack,直到没有,则指针指向左子树,然后在循环放入栈,放入结果中,然后再把结果数组反序即可。*/
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> result;
        if(!root){
            return result;
        }
        stack<TreeNode*> s;
        TreeNode *cur = root;
        while(!s.empty()||cur){
            while(cur){
                result.push_back(cur->val);//输出
                s.push(cur);//压站
                cur = cur->right;
            }
            /*最右边压站完成,弹出顶元素,然后指向他的左孩子,继续循环压站*/
            cur = s.top();
            s.pop();
            cur = cur->left;
        }

        reverse(result.begin(),result.end());
        return result;
    }

2.就是从出栈的角度进行处理

这个方式后序进行补充,感觉第一种方式实现起来更加的简单。