二叉树前序遍历非递归写法

转载自:http://blog.csdn.net/zhangxiangdavaid/article/details/37115355

前言

在前两篇文章二叉树二叉搜索树中已经涉及到了二叉树的三种遍历。递归写法,只要理解思想,几行代码。可是非递归写法却很不容易。这里特地总结下,透彻解析它们的非递归写法。其中,中序遍历的非递归写法最简单,后序遍历最难。我们的讨论基础是这样的:    

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //Binary Tree Node  
  2. typedef struct node  
  3. {  
  4.     int data;  
  5.     struct node* lchild;  //左孩子  
  6.     struct node* rchild;  //右孩子  
  7. }BTNode;  

首先,有一点是明确的:非递归写法一定会用到栈,这个应该不用太多的解释。我们先看中序遍历:

中序遍历

分析

中序遍历的递归定义:先左子树,后根节点,再右子树。如何写非递归代码呢?一句话:让代码跟着思维走。我们的思维是什么?思维就是中序遍历的路径。假设,你面前有一棵二叉树,现要求你写出它的中序遍历序列。如果你对中序遍历理解透彻的话,你肯定先找到左子树的最下边的节点。那么下面的代码就是理所当然的:

中序代码段(i)    
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. BTNode* p = root;  //p指向树根  
  2. stack<BTNode*> s;  //STL中的栈  
  3. //一直遍历到左子树最下边,边遍历边保存根节点到栈中  
  4. while (p)  
  5. {  
  6.     s.push(p);  
  7.     p = p->lchild;  
  8. }  

保存一路走过的根节点的理由是:中序遍历的需要,遍历完左子树后,需要借助根节点进入右子树。代码走到这里,指针p为空,此时无非两种情况:


说明:

  1. 上图中只给出了必要的节点和边,其它的边和节点与讨论无关,不必画出。
  2. 你可能认为图a中最近保存节点算不得是根节点。如果你看过树、二叉树基础,使用扩充二叉树的概念,就可以解释。总之,不用纠结这个没有意义问题。
  3. 整个二叉树只有一个根节点的情况可以划到图a。
仔细想想,二叉树的左子树,最下边是不是上图两种情况?不管怎样,此时都要出栈,并访问该节点。这个节点就是中序序列的第一个节点。根据我们的思维,代码应该是这样:   
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. p = s.top();  
  2. s.pop();  
  3. cout << p->data;  

我们的思维接着走,两图情形不同得区别对待:
1.图a中访问的是一个左孩子,按中序遍历顺序,接下来应访问它的根节点。也就是图a中的另一个节点,高兴的是它已被保存在栈中。我们只需这样的代码和上一步一样的代码:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. p = s.top();  
  2. s.pop();  
  3. cout << p->data;  
  
左孩子和根都访问完了,接着就是右孩子了,对吧。接下来只需一句代码:p=p->rchild;在右子树中,又会新一轮的代码段(i)、代码段(ii)……直到栈空且p空。

2.再看图b,由于没有左孩子,根节点就是中序序列中第一个,然后直接是进入右子树:p=p->rchild;在右子树中,又会新一轮的代码段(i)、代码段(ii)……直到栈空且p空。
思维到这里,似乎很不清晰,真的要区分吗?根据图a接下来的代码段(ii)这样的:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. p = s.top();  
  2. s.pop();  
  3. cout << p->data;  
  4. p = s.top();  
  5. s.pop();  
  6. cout << p->data;  
  7. p = p->rchild;  

根据图b,代码段(ii)又是这样的:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. p = s.top();  
  2. s.pop();  
  3. cout << p->data;  
  4. p = p->rchild;  

我们可小结下:遍历过程是个循环,并且按代码段(i)、代码段(ii)构成一次循环体,循环直到栈空且p空为止。   
不同的处理方法很让人抓狂,可统一处理吗?真的是可以的!回顾扩充二叉树,是不是每个节点都可以看成是根节点呢?那么,代码只需统一写成图b的这种形式。也就是说代码段(ii)统一是这样的:
中序代码段(ii)   
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. p = s.top();  
  2. s.pop();  
  3. cout << p->data;  
  4. p = p->rchild;  

口说无凭,得经的过理论检验。
图a的代码段(ii)也可写成图b的理由是:由于是叶子节点,p=-=p->rchild;之后p肯定为空。为空,还需经过新一轮的代码段(i)吗?显然不需。(因为不满足循环条件)那就直接进入代码段(ii)。看!最后还是一样的吧。还是连续出栈两次。看到这里,要仔细想想哦!相信你一定会明白的。

这时写出遍历循环体就不难了:     
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. BTNode* p = root;  
  2. stack<BTNode*> s;  
  3. while (!s.empty() || p)  
  4. {  
  5.     //代码段(i)一直遍历到左子树最下边,边遍历边保存根节点到栈中  
  6.     while (p)  
  7.     {  
  8.         s.push(p);  
  9.         p = p->lchild;  
  10.     }  
  11.     //代码段(ii)当p为空时,说明已经到达左子树最下边,这时需要出栈了  
  12.     if (!s.empty())  
  13.     {  
  14.         p = s.top();  
  15.         s.pop();  
  16.         cout << setw(4) << p->data;  
  17.         //进入右子树,开始新的一轮左子树遍历(这是递归的自我实现)  
  18.         p = p->rchild;  
  19.     }  
  20. }  

仔细想想,上述代码是不是根据我们的思维走向而写出来的呢?再加上边界条件的检测,中序遍历非递归形式的完整代码是这样的:
中序遍历代码一          
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //中序遍历  
  2. void InOrderWithoutRecursion1(BTNode* root)  
  3. {  
  4.     //空树  
  5.     if (root == NULL)  
  6.         return;  
  7.     //树非空  
  8.     BTNode* p = root;  
  9.     stack<BTNode*> s;  
  10.     while (!s.empty() || p)  
  11.     {  
  12.         //一直遍历到左子树最下边,边遍历边保存根节点到栈中  
  13.         while (p)  
  14.         {  
  15.             s.push(p);  
  16.             p = p->lchild;  
  17.         }  
  18.         //当p为空时,说明已经到达左子树最下边,这时需要出栈了  
  19.         if (!s.empty())  
  20.         {  
  21.             p = s.top();  
  22.             s.pop();  
  23.             cout << setw(4) << p->data;  
  24.             //进入右子树,开始新的一轮左子树遍历(这是递归的自我实现)  
  25.             p = p->rchild;  
  26.         }  
  27.     }  
  28. }  

恭喜你,你已经完成了中序遍历非递归形式的代码了。回顾一下难吗?
接下来的这份代码,本质上是一样的,相信不用我解释,你也能看懂的。
中序遍历代码二   
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //中序遍历  
  2. void InOrderWithoutRecursion2(BTNode* root)  
  3. {  
  4.     //空树  
  5.     if (root == NULL)  
  6.         return;  
  7.     //树非空  
  8.     BTNode* p = root;  
  9.     stack<BTNode*> s;  
  10.     while (!s.empty() || p)  
  11.     {  
  12.         if (p)  
  13.         {  
  14.             s.push(p);  
  15.             p = p->lchild;  
  16.         }  
  17.         else  
  18.         {  
  19.             p = s.top();  
  20.             s.pop();  
  21.             cout << setw(4) << p->data;  
  22.             p = p->rchild;  
  23.         }  
  24.     }  
  25. }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值