Chinaunix首页 | 论坛 | 博客
  • 博客访问: 390056
  • 博文数量: 62
  • 博客积分: 388
  • 博客等级: 一等列兵
  • 技术积分: 1032
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-03 20:18
文章分类

全部博文(62)

文章存档

2017年(5)

2016年(3)

2015年(3)

2014年(8)

2013年(15)

2012年(28)

分类:

2012-02-07 09:42:48

本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
 

最近需要复习一下基础算法,今天先来个二叉树的几种遍历方式的伪代码吧。
其中每种遍历方式都分为递归和非递归两种。

下面的代码我没有进行优化,可能代码有些冗余,不够简洁。因为没有真正的进行严格的测试,只是在纸上画了话,走了一下流程,欢迎大家指正错误。在代码中使用access作为访问节点数据的方式。其实最好的方法是将access作为类型为函数指针的参数,但是今天只为了温习一下实现,而不是真正的去写代码。就按照简单的来了。

对于非递归的实现,在上学的时候,当时并没有真正理解为什么要这样实现,来消除递归。现在我会把将递归转为非递归的过程描述得清楚一些。

1. 先序遍历:先访问根节点,然后遍历左子树,最后遍历右子树
a) 递归算法
  1. void preorder_traverse(const struct bi_tree *tree)
  2. {
  3.     if (tree) {
  4.         /* 访问根节点*/
  5.         access(tree->data);

  6.         if (tree->left) {
  7.             /* 访问左节点 */
  8.             preorder_traverse(tree->left);
  9.         }

  10.         if (tree->right) {
  11.             /* 访问右节点 */
  12.             preorder_traverse(tree->right);
  13.         }
  14.     }
  15. }
递归实现太简单了,而且非常清晰,便于理解。

b) 非递归算法
  1. void preorder_traverse(const struct bi_tree *tree)
  2. {
  3.     /*
  4.     为什么要选择用栈呢。
  5.     当我们遍历先序遍历二叉树时,按照先序的定义,先根节点,然后左节点,然后在右节点。
  6.     这样当按照不断地访问左节点而向下时,为了以后打印右节点,所以需要保存右节点。
  7.     之所以选择栈保存,因为打印右节点顺序与栈的特性先进后出相符,所以选择栈来保存右节点。
     后面的其他遍历方式,同样因为上面这个原因选择栈作为非递归遍历的一个保存方式。
  1.     */
  2.     struct stack s;

  3.     init_stack(&s);

  4.     const struct node *n = tree;

  5.     while (n) {
  6.         /* 访问节点 */
  7.         access(n->data);
         
         /* 该节点有左子节点,需要不断的往下遍历 */
  1.         while (n->left) {
  2.             /* 有右节点,那么将右节点压栈 */
  3.             if (n->right) {
  4.                 s.push(n->right);
  5.             }
  6.             
  7.             /* 移到左节点 */
  8.             n = n->left;
  9.             /* 访问该节点,在这里也可看作新的根节点 */
  10.             access(n->data);
  11.         }
       
         /* 开始访问右节点 */
         if (n->right) {
             /* 移到右节点 */
             n = n->right;
         }
         else {
             /* 需要弹出上一个保存的右节点 */
             n = s.pop();
         }
  1.     }
  2. }

2. 中序遍历:先遍历左子树,然后根节点,最后是右子树
a) 递归算法
  1. void midorder_traverse(const struct bi_tree *tree)
  2. {
  3.     if (tree->left) {
  4.         midorder_traverse(tree->left);
  5.     }
  6.     access(tree->data);
  7.     if (tree->right) {
  8.         midorder_traverse(tree->right);
  9.     }
  10. }
递归实现就是简单啊

b) 非递归算法
  1. void midorder_traverse(const struct bi_tree *tree)
  2. {
  3.     struct stack s;

  4.     init_stack(&s);

  5.     const struct node *n = tree;
  6.     
  7.     while (n) {
         /* 不断的沿左节点向下走 */
  1.         while (n->left) {
  2.             s.push(n);
  3.             n = n->left;
  4.         }
          
         /* 访问节点*/
  1.         access(n->data);
         
         /* 弹出父节点 */
  1.         while (n = s.pop()) {
  2.             access(n->data)
  3.             if (n->right) {
  4.                 /* 如果父节点有右节点,那么需要再次检查该右节点的左节点*/
  5.                 break;
  6.             }
  7.             /* 没有右节点,继续弹出保存的节点 */
  8.         }        
  9.     }
  10. }

3. 后序遍历:先左节点,然后右节点,最后根节点
a) 递归算法
  1. void lastorder_traverse(const struct bi_tree *tree)
  2. {
  3.     if (tree->left) {
  4.         lastorder_traverse(tree->left);
  5.     }
  6.     if (tree->right) {
  7.         lastorder_traverse(tree->right);
  8.     }
  9.     access(tree->data);
  10. }
不得不感叹递归的实现太简单了。。。

b) 非递归算法
之前的写法有错误,当从栈中弹出节点时,且该节点又有右子节点时,需要再次尝试先遍历该右子节点的所有左子节点时,会将该节点丢失。
那么,这时需要重新将该节点压栈,但是当再次弹出时,需要区分其右子节点是否已经遍历过了。所有在节点中添加了一个成员变量access。当被access函数访问时,该access被置位。

还有一个想法,看是否能够避免引入这个成员变量,暂时还没有答案。

  1. void lastorder_traverse(const struct bi_tree *tree)
  2. {
  3.     struct stack s;

  4.     init_stack(&s);

  5.     const struct node *n = tree;
  6.     
  7.     while (n) {
  8.         /* 沿左节点向下走 */
  9.         while (n->left) {
  10.             s.push(n);
  11.             n = n->left;
  12.         }

  13.         do {
  14.             /* 如果有右节点,仍然需要检查左节点 */
  15.             if (n->right && !n->right->access) {
  16.                 /* 右节点还未访问过,需要把该节点再次压栈 */
  17.                 s.push(n);
  18.                 n = n->right;
  19.                 break;
  20.             }
             
             /* 没有右节点,访问节点, 并弹出上一个父节点 */
  1.             access(n->data);
  2.         } while (n = s.pop());
  3.     }
  4. }

4. 按层访问:从根节点,由上及下,由左向右访问节点
a) 递归算法:
这个。。。这个。。。似乎没有递归的用武之地呵。。。

b) 非递归算法:
  1. void layorder_traverse(const struct bi_tree *tree)
  2. {
  3.     /*
  4.     这里选用queue而非stack,因为这种遍历方式需要的是FIFO,即先进先出 
  5.     */
  6.     struct queue q;

  7.     init_queue(&q);

  8.     const struct node *n = tree;

  9.     while (n) {
  10.         /* 访问节点 */
  11.         access(n->data);

  12.         if (n->left) {
  13.             /* 将左节点放入队列 */
  14.             q.push(n);
  15.         }

  16.         if (n->right) {
  17.             /* 将右节点放入队列 */
  18.             q.push(n);
  19.         }
         
         /* 取出下一个节点 */
  1.         n = q.pop();
  2.     }
  3. }

再次说明,以上代码未经测试,只是我在纸上简单的画了画,想了一下流程。思路基本上应该没有什么问题,再次欢迎大家指正。

阅读(1637) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~