二叉树的递归遍历与非递归算法实现

     通过递归算法与非递归算法的比较,更好地理解各自的特点。非递归其实就是调用栈的基本操作,进栈,出栈等。
这里面也正好复习了下栈的基本算法的实现。
 
栈和队列的实现在我的前一篇博文里。
 
基本数据结构
typedef  struct  BiNode
{
       char  data;   //此处,二叉树中节点值类型为字符型
       struct  BiNode *lchild,*rchild;     //左右孩子节点
}BiNode,*BiTree;   
 
二叉树的创建
先申请根节点的空间,然后赋值,然后分别递归建立其左子树和右子树
//按照先序序列输入构建一棵二叉树
View Code

 

二叉树的递归遍历算法

//二叉树的遍历,三种顺序的递归遍历,其实就是访问根节点的顺序不同,这里的访问操作是打印结点,也可以是对结点其他的操作
 
View Code

 

 
二叉树的非递归遍历算法
 
二叉树先序遍历的非递归算法
复制代码
/* 设置一个存放结点指针的栈 S ,从根结点开始,每访问一结点后,按先序规则走左子树,若右子树存在,

则将右子树指针进栈,以便以后能正确地返回到该右子树上进行遍历访问。

*/

void PreTraverse(BiTree T)

{

     BiNode *p;

     Stack S;

     S=InitStack();

      if (T)

     {

           Push(S,T);  // 根结点指针进栈

            while (!StackEmpty(S))

           {

                p=Pop(S);  // 出栈,栈顶元素赋给 p

                 while (p)

                {

                     printf( "%c\t" ,p->data);  // 访问 p结点

                      if (p->rchild)

                           Push(S,p->rchild);  // 右子树存在时,进栈

                     p=p->lchild;            // 继续沿着左子树往下走

                }

           }

     }

}
复制代码

/*

说明:内部循环是从  p  结点出发一直走到最左,走的过程中保存了每一个右子树的地址,
(因为右子树还没有被访问)而且是先进后出的,即先保存的比后保存的更先被用作返回地址,
所以是用栈。外循环正好是当内部循环不下去的时候,退一栈的情形。即换成他的右子树。
*/
 
//  二叉树的中序遍历的非递归算法
复制代码
/*

同前序遍历 , 栈S 存放结点指针。对每棵子树 ( 开始是整棵二叉树 ),沿左找到该子树在中序下的第一结点

( 但寻找路径上的每个结点指针要进栈 ), 访问之; 然后遍历该结点的右子树 ,

又寻找该子树在中序下的第一结点, .. …直到栈S 空为止。

*/


void InTraverse(BiTree T)

{

     BiNode *p;

     Stack S;

     S=InitStack();

     Push(S,T);  // 根结点指针进栈

      while (!StackEmpty(S))

     {

            while ((p=GetsTop(S))&&p)  //取栈顶元素且存在,赋给 p

                Push(S,p->lchild);    //p 的左子树进栈

           p=Pop(S);               // 去掉最后的空指针

            if (!StackEmpty(S))

           {

                p=Pop(S);       // 弹出栈顶元素,赋给 p

                printf( "%c\t" ,p->data);   // 访问 p结点

                Push(S,p->rchild);     // 右子树进栈,然后遍历右子树

           }

     }

}
复制代码

 /*

说明:和前序不一样,这里的栈保存的是根结点的地址(因为中序遍历先访问左子树,
而根结点没有被访问到。而前序遍历不一样,他一开始就访问根结点,
所以他不保存根结点的地址而是保存右子树的地址,因为右子树还没有被访问。
总之,用栈就是为了帮我们保存还没有被访问的地址,以便将来我们能找到返回的地址)。
*/
 
/*  后序遍历二叉树的非递归算法  */
复制代码
/* 对一个结点是否能访问,要看他的左、右子树是否遍历完, */

/* 所以每一个结点对应一个标志位 -tag 。tag=0 ,表示该结点暂不能访问; tag=1 ,表示该结点可以访问 */

/* 其实是区分这次返回是遍历完左子树返回的还是遍历完右子树返回的,如果是左子树返回的那么就不能访问根结点, */

/* 如果是右子树返回的就能访问根结点。当搜索到某 P 结点时,先要遍历其左子树,因而将结点地址 P 及tag=0 进栈; */

/* 当P 结点的左子树遍历完之后,再遍历其右子树,又将地址 P 及tag=1 进栈;当 P结点右子树遍历完之后( tag=1 ),便可对 P结点进行访问 */

void PostTraverse(BiTree T)

{

      int tag;

     BiNode *p;

     Stacks S;

     SNode sdata;

     S=InitStacks();

     p=T;

      while (p||!StacksEmpty(S))

     {

            while (p)

           {

                sdata.q=p;

                sdata.tag=0;

                Pushs(S,&sdata);   //(p,0) 进栈

                p=p->lchild;      // 遍历p 之左子树

           }

           sdata=*Pops(S);  // 退栈

           p=sdata.q;     // 取指针

           tag=sdata.tag; // 状态位

            if (tag==0)     //从左子树返回时,根的 tag=0

           {

                sdata.q=p;

                sdata.tag=1;      // 这时要进入根的右子树了,所以根的 tag=1 ,下次碰到根时就可以访问了

                Pushs(S,&sdata);   //(p,1) 进栈,根还得进一次栈

                p=p->rchild;     // 遍历右子树

           }

            else           //tag=1,这是说明了右子树访问完了返回,所以这次要对根进行访问了

           {

                printf( "%c\t" ,p->data);

                p=NULL;

           }

     }

}
复制代码

 

二叉树的层次遍历
复制代码
// 二叉树的层次遍历

void LevelTraverse(BiTree T)

{

     BiNode *p;

     LinkQueue *Q;

     InitQueue(Q);

     EnQueue(Q,T);

      while (!QueueEmpty(Q))

     {

           p=DeQueue(Q);

           printf( "%c\t" ,p->data);

            if (p->lchild!=NULL)

                EnQueue(Q,p->lchild);

            if (p->rchild!=NULL)

                EnQueue(Q,p->rchild);

     }

}
复制代码

 

下面是完整代码:
View Code

 

/* 测试数据 */
/*  分别是构建二叉树的输入数据以及先序、中序、后序、层次遍历序列:
ABE##C#D##F#GH#I##J##
ABECDFGHIJ
EBCDAFHIGJ
EDCBIGJGFA
ABFECGDHJI
 */
图画得有点丑,见笑了。

你可能感兴趣的:(二叉树的递归遍历与非递归算法实现)