树的先序,中序,后序遍历(非递归算法)
2014-04-21 16:43
155 查看
咔咔咔!终于等到做数据结构试验的时候啦!其实早就想把树的各种遍历的非递归算法给写了。但是想想数据结构的第二次试验写的就是树,于是就一直拖到了现在。代码的话,上周五就搞定了,不过最近作业着实太多,整个周末都用来写汇编试验了....单单报告就抄了一本本子
所以趁这个晚上试验前的一个空隙把这篇等了很久的博文给写了吧!
其实关于树的先序遍历的非递归算法,数据结构的书上面有。上课的时候也没仔细思考,只是知道要用栈。至于为什么要用栈?栈中存放的到底是什么内容?后序遍历还能用栈么?这些问题也没多想。不过通过这次树的实验,经过仔细思考,对于栈的提出及使用应该有了一定程度的理解了。
首先来讲讲先序遍历吧,附上代码,按照代码来分析好了~
树的先序非递归遍历:
首先,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 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; }
哈哈哈~树的基本操作差不多勒,下面应该会跟着《算法导论》走,努力把里面的算法基本都实现一遍的!!!
相关文章推荐
- css 中input和select混排对齐问题
- ajaxFileUpload js判断类型
- 当鼠标移动到图片上会显示 不同的背景
- 浪迹天涯,总在落叶的季节里
- DAVINCI DM3730开发攻略——U-BOOT-2010.06的移植 推荐
- 我的Qt学习笔记 2 QString的一些用法总结(1 section, split 函数)
- Entity Framework 4.1
- SAP 调用RFC 的时候记录异常报错方式
- Curator 学习 序
- 文件系统
- linux enca 搞定文件编码转换
- iOS 本地推送
- Android 验证输入的手机和邮箱字符串格式是否正确
- 短信PDU协议简介
- windows批处理编程
- java 命令行启动程序,附带jar包路径
- MFC Listbox创建右键菜单详解
- STL学习中。。。
- Mongodb简单的增删查改
- 为我的Junit的虚拟连接池加上Wizard