关于二叉树的非递归算法总结

                        关于二叉树的非递归遍历算法总结

开头还是那句 我只是个菜鸟 c语言新手 ,大神请无视该文章 ;不过这次的内容不适合纯新手  ,如果是纯新手, 也请忽视(还是先学好基础吧),因为需要你有些基础才看的明白
最近学数据结构时,看到二叉树这里来了,然后书上讲了二叉树的遍历问题,前面讲的是用递归算法遍历,用递归算法遍历比较容易理解,
(虽然要完全没看过书的话自己写该算法也写不出来,算法本身不是很容易想,但看源码后比较好理解),而用非递归算法来说的话,就不好
理解了(至少我是这么觉得的),好了,不多说废话,下面来开始总结吧。

本文档有60%左右是来自书上的
首先我看的数据结构书中是用二叉链来存储数据结构的,先看看二叉树结点的结构:

typedef struct node  //二叉树结点的结构
{
 ElemType data;   //存储二叉树中结点的值 ElemType是自定义的类型标示符
 struct node *lchild;   //二叉树的左孩子树
 struct node *rchild;   //二叉树的右孩子树
 
}BTNode;

 可以看出,该结构本身是一个递归的结构,所以关于二叉树的算法一般用递归写比较自然(那为什么要用非递归去写?因为想锻炼自己的能力嘛)
 然后可以看看二叉树的二叉链创建方式:
 
 //创建二叉树
void CreateBTNode(BTNode *&b,char *str)   //str存的是一个字符串,该字符串是二叉树的括号表示法 比如A(B(D(,G)),C(E,F)) 括号外面的是括号里面的根结点
{
 BTNode *p,*SC[MaxSize];  //p是临时存储变量 Sc是一个栈
 char ch;
 int k,j=0,top=-1; //top是栈的栈顶指针,j是字符数组str的下标,数组第一个字符为0,所以j取默认值为0 k是一个标志位,k=1是表示上一个结点为左结点,k=2是上一个结点为右结点
 ch=str[j]; //用ch去一个个的取str中的值
 b=NULL; //b作为存放二叉树的变量,一开始赋为空值
 while(ch!='\0')  //当str没有到最后的字符串结尾时
 {
        switch(ch) 
  {
  case '(': //表示上一个结点(p)存的是根结点
   top++;
   SC[top]=p;
   k=1;  //下一个结点应该是左结点
   break;
  case ')': //一个根结点遍历完了 出栈
   top--;
   break;
  case ',': //表示下一个结点应该是右结点 所以置标志位为2
   k=2;
   break;
    default:   //当ch取得的字符是字母时,表示是一个结点
   p=(BTNode*)malloc(sizeof(BTNode));
   p->data=ch;  //p的结点赋值
   p->lchild=p->rchild=NULL;  //p的左子树和右子树先赋为空
   if(b==NULL)  //如果是一开始,即是根结点,则直接把p(根结点)赋给b
    b=p;
   else
   {
        switch(k)  //判断标志位
    {
    case 1:  //为1表示p存的是左结点
     SC[top]->lchild=p;
     break;
    case 2: //为2表示p存的是右结点
     SC[top]->rchild=p;
     break;

    }

   }

  }
        j++;
  ch=str[j];


    }
   
}

这样,通过上面的代码,b里面存的就是一个二叉树的结构了,下面就开始讲怎么遍历二叉树
二叉树的遍历: 二叉树的遍历是指按照一定的次序访问二叉树中的所有结点,并且每个结点仅被访问一次的过程
先序遍历 :根结点->左孩子结点->右孩子结点
中序遍历 :左孩子结点->根结点->右孩子结点
后序遍历 : 左孩子结点->右孩子结点->根结点

这里就以打印的方式表示遍历了该结点

先来看看怎么用递归的方法遍历二叉树:
先序遍历:
void Proorder(BTNode *p)
{
 if(p!=NULL)  //这里要注意了 一定要判断一下空,不然是会程序崩溃的(尤其是新手要注意用指针时要多判断空啊)
 {
      printf("%c ",p->data);  //先直接打印该结点
     Proorder(p->lchild);  //递归遍历左子树
     Proorder(p->rchild);  //递归遍历右子树
    }
}

以上的代码看起来比较简单,但要你自己不看写出来,也较难想到吧,不过看了后觉得思维很清晰
中序遍历:
void Inorder(BTNode *p)
{
 if(p!=NULL)
 {
        Inorder(p->lchild);
     printf("%c ",p->data);
     Inorder(p->rchild);
 }
}

后序遍历:
void Posorder(BTNode *p)
{
 if(p!=NULL)
 {
     Posorder(p->lchild);
     Posorder(p->rchild);
     printf("%c ",p->data);
 }
}

递归遍历二叉树的算法清晰明了,下面就来讨论非递归遍历二叉树吧:
我个人认为在非递归遍历二叉树中 难度是 :先序遍历 < 中序遍历 < 后序遍历 这些算法都比较巧妙 我这个菜鸡基本上写不出来是看的书上的


首先想想,既然用的是非递归,要想遍历所有的结点,肯定就要用循环了,然后因为要用之前遍历过的结点(有位置信息),由于二叉链的链接是
单向的(不能反向去找先的结点),所以需要一个结构去存储之前的结点,这个结构就是栈,栈真的是一个很好的结构去存东西,递归本身的原理也是用到栈来实现的.
然后就是要注意遍历的顺序了.

以下的算法我自己都写不出来 都是书上的
先序遍历非递归算法:
void PreOrder1(BTNode *b)
{
 BTNode *St[MaxSize],*p;  //St就是存储结点的栈 p是作为结点遍历的过渡变量
 int top=-1;  //栈顶指针
 if(b!=NULL)  //注意判断空
 {
  top++;
  St[top]=b;   //先将根结点进栈
  while(top>-1)   //栈不为空时循环
  {
   p=St[top];
   top--; //出栈一个元素
   printf("%c ",p->data);
   if(p->rchild!=NULL)  //如果p有右孩子结点
   {
    top++; //进栈
    St[top]=p->rchild;
   }

   if(p->lchild!=NULL)  //如果p有左孩子结点
   {
    top++;
    St[top]=p->lchild;
   }


  }
  printf("\n");
 }
}

是不是非常的巧妙,如果有什么不懂的就自己去悟吧,先序遍历和中序遍历看了代码后不需要说的太多

中序遍历非递归算法:
void zhongf(BTNode *b)
{
 
    BTNode *St[MaxSize];
   BTNode *p;
    int top=-1;
   if(b!=NULL)
   {
    p=b;
    while(top>-1|| p!=NULL)  //注意这里有2个判断的条件 这里不太好想
    {
   while(p!=NULL)  //先一直找其左结点,直到找不到为止
   {
    top++;
    St[top]=p;
    p=p->lchild;
   }
   if(top>-1)  //上面的p=NULL了并且如果栈中还有结点 (栈中还有结点说明了还有结点没被遍历即输出)
   {
    p=St[top];  //先把p还原成栈顶结点(不然p还是空)
    top--;  //出栈  注意代码的顺序
    printf("%c ",p->data);
    p=p->rchild;  //准备遍历右孩子结点
   }


  }
  printf("\n");

 }
 
}

//后序遍历非递归算法:
由于后序遍历是先访问其左,右孩子结点,而后才访问根结点,所以在访问根结点之前要说明其左,右结点是否已经访问过,其中的难点是
怎么判断一个结点的右结点已经访问过,(左结点肯定在右结点之前访问),因为是后序遍历,实际上,当*b的右结点访问过时,其整个右子树
就都被访问过了,用p代表上一个刚被(不是钢背兽哦)访问过的结点,如果b->rchild==p 这个条件成立,表示其右子树都被访问过,就该直接访问*b了


这个算法很精辟,很美
void houf(BTNode *b)      //书上的原算法是直接用b 这里我想保留b 所以多用了一个变量bb  不想破坏原来的b中的值
{
    BTNode* St[MaxSize];
 BTNode *p,*bb=b;   //p的作用是存储(栈顶结点的)前一个刚刚访问的结点
 int flag,top=-1;   //flag=1表示*b的左孩子已访问过或为空  top是栈顶的指针
 if(bb!=NULL) //注意判断空
 {
    do    //只要栈不为空
    {
     while(bb!=NULL)   //将*bb的所有左结点进栈
     {
      top++;
      St[top]=bb;
      bb=bb->lchild;
            
     }
     //执行到此处时 栈顶元素没有左孩子结点 或左子树均已访问过
     p=NULL;  //p的作用是存储(栈顶结点的)前一个已访问的结点  初始值置为空
     flag=1;   //flag=1表示*b的左孩子已访问过或为空   因为栈顶元素的左孩子结点为空所以先初始化为1
     while(top!=-1 && flag==1)  //栈不为空,并且左结点已访问过
     {
      bb=St[top];  //取出当前的栈顶元素

      /*    bb->rchild==p中
               若p=NULL,表示b的右孩子不存在,而其左子树已访问或不存在(flag=1),所以可以访问*b;
               若p!=NULL,表示b的右孩子已访问 因为p是刚刚被访问的结点,bb->rchild==p表示 bb的右孩子刚刚被访问过
      有一点要注意,因为是后序遍历,当*b的右孩子结点已经被访问过的话,*b的右子树肯定都被访问了
      (原因是p指向b的右子树中刚访问过的结点,而*p是b的右孩子,*p一定是b的
      右子树中后序序列的最后一个结点,所以可以访问*b
               */
      if(bb->rchild==p) 
      {
       printf("%c ",bb->data);   //访问*b结点
       top--;
       p=bb;  //p保存上一个已访问过的结点
      }
      else  //若b的右孩子结点没有被访问过,由于后序遍历,则需要先访问其右结点
      {
       bb=bb->rchild;
       flag=0;  //新结点的左子树肯定没被访问过,这也是暂时出这个while循环的条件
      }
     }

     
    } while(top!=-1);
    printf("\n");
  
  
 }


}

好啦 总结完毕(ps:我写这篇文档时,我旁边的同事都在打dota 不停地叫,吸引我注意力 哎)! 




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值