咔咔咔!终于等到做数据结构试验的时候啦!其实早就想把树的各种遍历的非递归算法给写了。但是想想数据结构的第二次试验写的就是树,于是就一直拖到了现在。代码的话,上周五就搞定了,不过最近作业着实太多,整个周末都用来写汇编试验了....单单报告就抄了一本本子所以趁这个晚上试验前的一个空隙把这篇等了很久的博文给写了吧!
其实关于树的先序遍历的非递归算法,数据结构的书上面有。上课的时候也没仔细思考,只是知道要用栈。至于为什么要用栈?栈中存放的到底是什么内容?后序遍历还能用栈么?这些问题也没多想。不过通过这次树的实验,经过仔细思考,对于栈的提出及使用应该有了一定程度的理解了。
首先来讲讲先序遍历吧,附上代码,按照代码来分析好了~
树的先序非递归遍历:
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;
}
哈哈哈~树的基本操作差不多勒,下面应该会跟着《算法导论》走,努力把里面的算法基本都实现一遍的!!!