关于二叉树的非递归遍历算法总结
开头还是那句 我只是个菜鸟 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 不停地叫,吸引我注意力 哎)!