您的位置:首页 > 其它

树的先序,中序,后序遍历(非递归算法)

2014-04-21 16:43 155 查看
咔咔咔!终于等到做数据结构试验的时候啦!其实早就想把树的各种遍历的非递归算法给写了。但是想想数据结构的第二次试验写的就是树,于是就一直拖到了现在。代码的话,上周五就搞定了,不过最近作业着实太多,整个周末都用来写汇编试验了....单单报告就抄了一本本子





所以趁这个晚上试验前的一个空隙把这篇等了很久的博文给写了吧!

其实关于树的先序遍历的非递归算法,数据结构的书上面有。上课的时候也没仔细思考,只是知道要用栈。至于为什么要用栈?栈中存放的到底是什么内容?后序遍历还能用栈么?这些问题也没多想。不过通过这次树的实验,经过仔细思考,对于栈的提出及使用应该有了一定程度的理解了。

首先来讲讲先序遍历吧,附上代码,按照代码来分析好了~

树的先序非递归遍历:

Status PreOrderTraverse(BiTree T,Status (*Visit)(ElemType e))
{//先序遍历二叉树T的非递归算法
    BiTree S[100];              //栈,且栈中保存的都是非空且已被访问左子树的元素
    BiTree P=T;                 //P作为当前访问子树的根节点
    int i=0;                    //栈顶指针
    while(P||i)                 //当前节点不为空或者栈不为空
    {
        if(P)                   //如果当前节点不为空
        {
            Visit(P->data);     //先序遍历根节点
            S[i++]=P;
            P=P->lchild;          //并且访问左子树
        }
        else                      //若P为空,则说明栈顶元素的左子树为空,访问右子树
        {
            P=S[i-1]->rchild;
            i--;                    //已进入右子树,栈顶已经没有意义了,所以退栈
        }
    }
}


首先,P是当前所在子树的根节点,i指向栈顶元素的上一个元素。而那个while循环里的判断条件就是要不P为空,或者栈非空。当P为空,说明P所在的子树为空树,即P已经已经访问完了一棵子树,则此时进入循环后应给P赋值栈顶的元素的右孩子,而栈顶需要退栈。因为此时该节点的左树已经遍历完,右树的话P已经进入,此时该节点就已经没有保留的必要了,因此退栈。当P为空,栈也为空时,说明已经没有要回溯的节点了,说明整棵树已经遍历完了,因此跳出循环。而当P非空时,说明P刚刚进了一棵新的子树,按照先序遍历的原则,应该先访问该根节点,然后进入左子树,但是我需要访问右子树,所以有必要将该节点保存下来,所以将该节点入栈,供P回溯时能够进入右子树进行遍历。总之,栈中就是按照先进后出的原则(因为将子树遍历完,才能遍历父树),将还未遍历右子树的根节点保存起来以供回溯。一旦P进入右子树后,该栈顶元素标记的作用就消失了,因此可以退栈。

至于中序遍历的话和先序遍历大致是一样的,就是将Visit函数的调用放到栈顶元素退栈之前即可,在这里就不再废话了。

总算说到最最关键的后序遍历了。之前一直在想,后序遍历到底能不能用栈来实现?是不是要用两个栈?而后序遍历的难点在于,它不同于先序元素,栈中的元素只要进入右树就能丢掉,因为它还要在右树遍历之后回到根节点,对根进行访问。因此栈顶元素在P进入右子树后不能退栈,而要保留。这时就又引申出一个问题,当P回溯的时候,栈顶的元素是还未遍历右树的节点呢?还是已经遍历左右树,只要访问一下就可以了?

解决方案其实也显而易见的啦,只要另外再设计一个标志数组,再将元素压栈时,在该数组与栈顶对应的位置的数组置0,说明右树未访问。而当P回溯的时候,先判断栈顶的标志元素,若为0,则说明右子树未访问,于是让P进入右树,并且将该栈顶的标志元素置1,以便再次回溯的时候说明该节点的左右子树都已经访问过了,只要访问根节点然后退栈即可。此时P的状态是不需要改变的,因为P也不知道应该进入哪棵子树,这时就需要重复访问栈顶,找到还未访问右子树的节点,然后P进入即可。不知道我表达的清楚不清楚,与先序遍历不同,栈中保存的节点有两个状态,一个是未访问右子树的,标志数组对应元素置0,另一个是已访问右子树的,置1,此类节点回溯的时候只要访问该节点并退栈即可。最后还是上代码吧~






Status PostOrderTraverse(BiTree T,Status (*Visit)(ElemType e))
{//后序遍历二叉树T的非递归算法
    BiTree S[100];                      //保存子树根节点的栈
    char f[100];                        //标志栈,节点入栈,相应元素置0,从左子树返回置1,右子树返回即可舍弃该标志数
    int i=0;                            //栈顶指针
    BiTree P=T;
    while(P||i)                         //当前子树不为空,或栈不为空
    {
        if(P)                           //树非空,则入左子树
        {
            S[i]=P,f[i]=0,i++;          //当前根节点入栈
            P=P->lchild;
        }
        else                            //为空则遍历完左子树或右子树
        {
            if(f[i-1])                  //若标志栈栈顶为1,说明从右子树返回
            {
                Visit(S[i-1]->data);    //这时并不需要给P赋值,只有在栈顶元素的标志为0时才需要进入右子树
                i--;
            }
            else
            {
                f[i-1]=1;                   //从左树返回,栈顶标志元素置1
                P=S[i-1]->rchild;
            }
        }
    }
    return OK;
}


哈哈哈~树的基本操作差不多勒,下面应该会跟着《算法导论》走,努力把里面的算法基本都实现一遍的!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: