遍历二叉树(非递归)

遍历二叉树(非递归)

博客链接:递归遍历二叉树

语言 = C++;
博客摘要:

  1. 回顾层序遍历的遍历方式

  2. 采用非递归的方式遍历二叉树;
    主要三种遍历方式:
    @前序遍历
    @中序遍历
    @后序遍历


在上一篇博客中我们提到了遍历二叉树的四种方式:

  1. 前序遍历
  2. 中序遍历
  3. 后序遍历
  4. 层序遍历

其中前三种采用了递归的方式,而层序遍历采用的是非递归的方式,为我们本篇博客坐了铺垫;

我们先来回忆一下层序遍历的大体过程;

用队列辅助存储结点的方式遍历,(具体过程请阅读博客:二叉树的遍历(递归));

代码回顾:

层序遍历
    void LevelOrder1()
    {
        queue<Node> q;  //队列(先进先出)
        q.push (_root); //根结点先存入队列

        while(!q.empty ())
        {
            Node top = q.front ();
            cout<<top->_data <<" ";
            q.pop ();
            if(top->_left )
                q.push (top->_left );
            if(top->_right )
                q.push (top->_right );
        }
    }

既然层序遍历可以通过队列的辅助实现,那么,前三种方式是不是也可以借助一种容器实现呢?
回忆一下我们学习递归的时候经常说得,递归的过程其实和压栈的过程差不多,我们是不是可以利用栈来实现非递归的二叉树遍历呢?

来试一下:
还是以这棵二叉树为例:
这里写图片描述

  1. 前序遍历:
    依然按照先访问当前结点,再访问左孩子,最后访问右孩子的顺序来实现!

如果借助栈来实现的话我们需要先访问当前结点,然后当前结点压栈(因为最后访问右孩子的时候需要)再指向当前结点的左孩子,直到左孩子为NULL;
再从栈顶取出元素访问右孩子(为什么从栈顶取? 因为栈顶是最后一个压入栈的结点说明她的左孩子为NULL,当前结点也访问过了,就该访问右孩子了),注意:这里右孩子也有可能有左右孩子,就得注意这里的循环问题了!!!

具体实例分析过程: (还是上图的例子)

先看代码再看分析过程:

void PrevOrder2()//前序遍历非递归
    {
        stack<Node> s;//栈
        Node cur = _root;//根结点

        while(cur || !s.empty ())
        {
            while(cur)
            {
                cout<<cur->_data <<" ";
                s.push (cur );
                cur = cur->_left ;
            }

            Node tmp = s.top ();
            s.pop();

            cur = tmp->_right;
        }
        cout<<endl;
    }

**首先我们有了一个栈:s
接着我们需要得到根节点cur = _root(根节点);**

正式开始:

如果当前的结点cur不为NULL或者栈不为NULL(需要用循环控制)我们就可以继续接下来的过程了;
为什么?
如果当前结点为NULL并且栈也为NULL的话,我们好像没有什么可以继续访问的不是吗?

如果 cur不为NULL的话(又是一个内循环),我们就先访问cur->_data( 1 )(先序遍历),然后指向 1 的节点指针入栈,然后让当前结点指向它的左孩子(2);cur = cur->_left;这就是前面设置循环条件为当前结点左孩子不为null的原因;

接下来直接用节点数据代表cur; 2不为NULL,2入栈,cur指向3;

3不为NULL,3入栈,cur指向3的左孩子即NULL;

cur指向NULL,可以访问栈顶元素的右孩子了,即3的右孩子,记得pop()掉栈顶元素,因为它的自身,左右孩子都访问过了;

cur = s.top()->_right;因为3的右孩子也可能有节点;
比如下图:

这里写图片描述

所以把3的右结点又当成是一个根节点来循环;

还是以第一个二叉树继续,cur为NULL,直接跳过内层循环,取栈顶元素2(3已经在上次循环时Pop()掉了);访问2的右孩子,pop栈顶元素,即又将2的右孩子当作根节点,cur = cur->_right;

cur指向4,进入内层循环,访问4;4入栈,cur = cur->_left;
cur为NULL,跳出内层循环,取栈顶元素4,pop栈顶元素,访问4的右孩子,cur = cur->_right;

cur为NULL,跳过内层循环,取栈顶元素1,cur指向它的右孩子,cur = cur->_right;

cur指向5,进入内层循环,访问5,5入栈,cur = cur->_left;

cur指向6,访问6,6入栈,出内层循环,取栈顶元素6,pop栈顶元素,cur = cur->_right;

cur为NULL, 跳过内层循环,取栈顶元素5,pop栈顶元素,cur = cur->_right;

cur为NULL并且栈为NULL, 结束!

以上就是走了一次前序遍历的非递归,中序遍历以此类推;

//中序遍历的非递归实现
    void Inorder2()
    {
        stack<Node> s;  
        Node cur = _root;

        while(cur || !s.empty ())
        {
            while(cur)
            {
                s.push (cur );
                cur = cur->_left ;
            }

            Node tmp = s.top ();
            cout<<tmp->_data <<" ";
            s.pop();

            cur = tmp->_right;
        }
        cout<<endl;
   }

中序遍历可以以此类推,而后序遍历没有以此类推的原因是,后序遍历略微有坑;

下面我们就详细讲述一下后序遍历;

结合代码看解释更好理解!!!

//后序遍历非递归
    void _PostOrder3(Node root)
    {
        stack<Node> s;
        Node cur = _root;  //保存当前结点
        Node prev = NULL;  //保存访问的前一个结点

        while(cur || !s.empty ())
        {
            while(cur)
            {
                s.push (cur);
                cur = cur->_left ;
            }

            Node top = s.top ();
            //判断当前结点是否可以访问的两个限定条件
            if(top->_right  == NULL || top->_right  == prev)
            {
                cout<<top->_data <<" ";
                prev = top;  //注意更新前一个访问的结点;
                s.pop ();
            }
            //否则说明当前结点的右孩子还没有访问过;
            else
                cur = top->_right ;
        }
    }

后序遍历的规则在于,先左后右,最后当前结点;那么我们用栈存储结点访问时,就会出现一种情况,比如不知道当前结点是否可以访问,因为有两种情况可以退回当前结点,比如刚访问过它的左孩子,退回到当前结点,然后去访问它的右孩子,又会退回到当前结点,所有就会出现这个
矛盾;

具体点的比如,下图:
这里写图片描述

当访问过3后,退回到2,2不能访问,因为要访问2的右孩子,那么去访问4,又退回2,那么问题来了,编译器可不知道你刚才访问的是你的左孩子还是右孩子;

这就需要我们想一种方法让程序知道当前结点是否可以访问了,还是上图为例,如果我们知道当前结点访问的上一个结点是什么,再与要访问的下一个结点比较,如果和下一个结点相同的话,就代表当前结点可以访问了,当然,还有还得判断一种为NULL的情况;

比如:后序遍历访问到4的时候,前一个访问的是3,而4的右孩子为NULL,岂不是不能访问4了,所以,我们还得考虑右孩子为NULL的情况;

后序遍历的演示过程就省略了,压栈过程借鉴前序遍历;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值