本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
最近需要复习一下基础算法,今天先来个二叉树的几种遍历方式的伪代码吧。
其中每种遍历方式都分为递归和非递归两种。
下面的代码我没有进行优化,可能代码有些冗余,不够简洁。因为没有真正的进行严格的测试,只是在纸上画了话,走了一下流程,
欢迎大家指正错误。在代码中使用access作为访问节点数据的方式。其实最好的方法是将access作为类型为函数指针的参数,但是今天只为了温习一下实现,而不是真正的去写代码。就按照简单的来了。
对于非递归的实现,在上学的时候,当时并没有真正理解为什么要这样实现,来消除递归。现在我会把将递归转为非递归的过程描述得清楚一些。
1. 先序遍历:先访问根节点,然后遍历左子树,最后遍历右子树
a) 递归算法
- void preorder_traverse(const struct bi_tree *tree)
- {
- if (tree) {
- /* 访问根节点*/
- access(tree–>data);
- if (tree–>left) {
- /* 访问左节点 */
- preorder_traverse(tree–>left);
- }
- if (tree–>right) {
- /* 访问右节点 */
- preorder_traverse(tree->right);
- }
- }
- }
递归实现太简单了,而且非常清晰,便于理解。
b) 非递归算法
- void preorder_traverse(const struct bi_tree *tree)
- {
- /*
- 为什么要选择用栈呢。
- 当我们遍历先序遍历二叉树时,按照先序的定义,先根节点,然后左节点,然后在右节点。
- 这样当按照不断地访问左节点而向下时,为了以后打印右节点,所以需要保存右节点。
- 之所以选择栈保存,因为打印右节点顺序与栈的特性先进后出相符,所以选择栈来保存右节点。
后面的其他遍历方式,同样因为上面这个原因选择栈作为非递归遍历的一个保存方式。
- */
- struct stack s;
- init_stack(&s);
- const struct node *n = tree;
- while (n) {
- /* 访问节点 */
- access(n–>data);
/* 该节点有左子节点,需要不断的往下遍历 */
- while (n–>left) {
- /* 有右节点,那么将右节点压栈 */
- if (n–>right) {
- s.push(n–>right);
- }
- /* 移到左节点 */
- n = n–>left;
- /* 访问该节点,在这里也可看作新的根节点 */
- access(n–>data);
- }
/* 开始访问右节点 */
if (n->right) {
/* 移到右节点 */
n = n->right;
}
else {
/* 需要弹出上一个保存的右节点 */
n = s.pop();
}
- }
- }
2. 中序遍历:先遍历左子树,然后根节点,最后是右子树
a) 递归算法
- void midorder_traverse(const struct bi_tree *tree)
- {
- if (tree–>left) {
- midorder_traverse(tree–>left);
- }
- access(tree–>data);
- if (tree–>right) {
- midorder_traverse(tree–>right);
- }
- }
递归实现就是简单啊
b) 非递归算法
- void midorder_traverse(const struct bi_tree *tree)
- {
- struct stack s;
- init_stack(&s);
- const struct node *n = tree;
- while (n) {
/* 不断的沿左节点向下走 */
- while (n–>left) {
- s.push(n);
- n = n->left;
- }
/* 访问节点*/
- access(n–>data);
/*
弹出父节点
*/
- while (n = s.pop()) {
- access(n->data)
- if (n–>right) {
- /* 如果父节点有右节点,那么需要再次检查该右节点的左节点*/
- break;
- }
- /* 没有右节点,继续弹出保存的节点 */
- }
- }
- }
3. 后序遍历:先左节点,然后右节点,最后根节点
a) 递归算法
- void lastorder_traverse(const struct bi_tree *tree)
- {
- if (tree–>left) {
- lastorder_traverse(tree–>left);
- }
- if (tree–>right) {
- lastorder_traverse(tree–>right);
- }
- access(tree–>data);
- }
不得不感叹递归的实现太简单了。。。
b) 非递归算法
之前的写法有错误,当从栈中弹出节点时,且该节点又有右子节点时,需要再次尝试先遍历该右子节点的所有左子节点时,会将该节点丢失。
那么,这时需要重新将该节点压栈,但是当再次弹出时,需要区分其右子节点是否已经遍历过了。所有在节点中添加了一个成员变量access。当被access函数访问时,该access被置位。
还有一个想法,看是否能够避免引入这个成员变量,暂时还没有答案。
- void lastorder_traverse(const struct bi_tree *tree)
- {
- struct stack s;
- init_stack(&s);
- const struct node *n = tree;
- while (n) {
- /* 沿左节点向下走 */
- while (n–>left) {
- s.push(n);
- n = n–>left;
- }
- do {
- /* 如果有右节点,仍然需要检查左节点 */
- if (n–>right && !n->right->access) {
- /* 右节点还未访问过,需要把该节点再次压栈 */
- s.push(n);
- n = n–>right;
- break;
- }
/* 没有右节点,访问节点, 并弹出上一个父节点 */
- access(n–>data);
- } while (n = s.pop());
- }
- }
4. 按层访问:从根节点,由上及下,由左向右访问节点
a) 递归算法:
这个。。。这个。。。似乎没有递归的用武之地呵。。。
b) 非递归算法:
- void layorder_traverse(const struct bi_tree *tree)
- {
- /*
- 这里选用queue而非stack,因为这种遍历方式需要的是FIFO,即先进先出
- */
- struct queue q;
- init_queue(&q);
- const struct node *n = tree;
- while (n) {
- /* 访问节点 */
- access(n–>data);
- if (n–>left) {
- /* 将左节点放入队列 */
- q.push(n);
- }
- if (n–>right) {
- /* 将右节点放入队列 */
- q.push(n);
- }
/* 取出下一个节点 */
- n = q.pop();
- }
- }
再次说明,以上代码未经测试,只是我在纸上简单的画了画,想了一下流程。思路基本上应该没有什么问题,再次欢迎大家指正。
<script>window._bd_share_config={“common”:{“bdSnsKey”:{},”bdText”:””,”bdMini”:”2″,”bdMiniList”:false,”bdPic”:””,”bdStyle”:”0″,”bdSize”:”16″},”share”:{}};with(document)0[(getElementsByTagName(‘head’)[0]||body).appendChild(createElement(‘script’)).src=’http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion=’+~(-new Date()/36e5)];</script> 阅读(275) | 评论(0) | 转发(0) |
0
下一篇:c语言宏定义总结
相关热门文章
- A sample .exrc file for vi e…
- IBM System p5 服务器 HACMP …
- 游标的特征
- DB2 9 应用开发(733 考试)认…
- busybox的httpd使用CGI脚本(Bu…
给主人留下些什么吧!~~ 评论热议