【数据结构与算法】二叉树递归与非递归遍历(附完整源码)(转)

转自:http://blog.csdn.net/ns_code/article/details/12977901

二叉树是一种非常重要的数据结构,很多其他数据机构都是基于二叉树的基础演变过来的。二叉树有前、中、后三种遍历方式,因为树的本身就是用递归定义的,因此采用递归的方法实现三种遍历,不仅代码简洁且容易理解,但其开销也比较大,而若采用非递归方法实现三种遍历,则要用栈来模拟实现(递归也是用栈实现的)。下面先简要介绍三种遍历方式的递归实现,再详细介绍三种遍历方式的非递归实现。

一、三种遍历方式的递归实现(比较简单,这里不详细讲解)

1、先序遍历——按照“根节点-左孩子-右孩子”的顺序进行访问。

  1.  1 void pre_traverse(BTree pTree)
     2 {
     3     if(pTree)
     4     {
     5         printf("%c ",pTree->data);
     6         if(pTree->pLchild)
     7             pre_traverse(pTree->pLchild);
     8         if(pTree->pRchild)
     9             pre_traverse(pTree->pRchild);
    10     }
    11 }  

2、中序遍历——按照“左孩子-根节点-右孩子”的顺序进行访问。

  1.  1 void in_traverse(BTree pTree)
     2 {
     3     if(pTree)
     4     {
     5         if(pTree->pLchild)
     6             in_traverse(pTree->pLchild);
     7         printf("%c ",pTree->data);
     8         if(pTree->pRchild)
     9             in_traverse(pTree->pRchild);
    10     }
    11 }  

3、后序遍历——按照“左孩子-右孩子-根节点”的顺序进行访问。

  1.  1 void beh_traverse(BTree pTree)
     2 {
     3     if(pTree)
     4     {
     5         if(pTree->pLchild)
     6             beh_traverse(pTree->pLchild);
     7         if(pTree->pRchild)
     8             beh_traverse(pTree->pRchild);
     9         printf("%c ",pTree->data);
    10 }  

二、三种遍历方式的非递归实现

为了便于理解,这里以下图的二叉树为例,分析二叉树的三种遍历方式的实现过程。

1、前序遍历的非递归实现

根据先序遍历的顺序,先访问根节点,再访问左子树,后访问右子树,而对于每个子树来说,又按照同样的访问顺序进行遍历,上图的先序遍历顺序为:ABDECF。非递归的实现思路如下:

对于任一节点P,

1)输出节点P,然后将其入栈,再看P的左孩子是否为空;

2)若P的左孩子不为空,则置P的左孩子为当前节点,重复1)的操作;

3)若P的左孩子为空,则将栈顶节点出栈,但不输出,并将出栈节点的右孩子置为当前节点,看其是否为空;

4)若不为空,则循环至1)操作;

5)如果为空,则继续出栈,但不输出,同时将出栈节点的右孩子置为当前节点,看其是否为空,重复4)和5)操作;

6)直到当前节点P为NULL并且栈空,遍历结束。

   下面以上图为例详细分析其先序遍历的非递归实现过程:

首先,从根节点A开始,根据操作1),输出A,并将其入栈,由于A的左孩子不为空,根据操作2),将B置为当前节点,再根据操作1),将B输出,并将其入栈,由于B的左孩子也不为空,根据操作2),将D置为当前节点,再根据操作1),输出D,并将其入栈,此时输出序列为ABD;

由于D的左孩子为空,根据操作3),将栈顶节点D出栈,但不输出,并将其右孩子置为当前节点;

由于D的右孩子为空,根据操作5),继续将栈顶节点B出栈,但不输出,并将其右孩子置为当前节点;

由于B的右孩子E不为空,根据操作1),输出E,并将其入栈,此时输出序列为:ABDE;

由于E的左孩子为空,根据操作3),将栈顶节点E出栈,但不输出,并将其右孩子置为当前节点;

由于E的右孩子为空,根据操作5),继续将栈顶节点A出栈,但不输出,并将其右孩子置为当前节点;

由于A的右孩子C不为空,根据操作1),输出C,并将其入栈,此时输出序列为:ABDEC;

由于A的左孩子F不为空,根据操作2),则将F置为当前节点,再根据操作1),输出F,并将其入栈,此时输出序列为:ABDECF;

由于F的左孩子为空,根据操作3),将栈顶节点F出栈,但不输出,并将其右孩子置为当前节点;

由于F的右孩子为空,根据操作5),继续将栈顶元素C出栈,但不输出,并将其右孩子置为当前节点;

此时栈空,且C的右孩子为NULL,因此遍历结束。

根据以上思路,前序遍历的非递归实现代码如下:

  1.  1 void pre_traverse(BTree pTree)
     2 {
     3     PSTACK stack = create_stack();  //创建一个空栈
     4     BTree node_pop;                 //用来保存出栈节点
     5     BTree pCur = pTree;             //定义用来指向当前访问的节点的指针
     6
     7     //直到当前节点pCur为NULL且栈空时,循环结束
     8     while(pCur || !is_empty(stack))
     9     {
    10         //从根节点开始,输出当前节点,并将其入栈,
    11         //同时置其左孩子为当前节点,直至其没有左孩子,及当前节点为NULL
    12         printf("%c ", pCur->data);
    13         push_stack(stack,pCur);
    14         pCur = pCur->pLchild;
    15         //如果当前节点pCur为NULL且栈不空,则将栈顶节点出栈,
    16         //同时置其右孩子为当前节点,循环判断,直至pCur不为空
    17         while(!pCur && !is_empty(stack))
    18         {
    19             pCur = getTop(stack);
    20             pop_stack(stack,&node_pop);
    21             pCur = pCur->pRchild;
    22         }
    23     }
    24 }  

    另一种思路

     1 #include<stack>
     2 void PreOrder(BinaryTreeNode* pRoot)
     3 {
     4     if (pRoot==NULL)
     5         return;
     6     std::stack<BinaryTreeNode*> S;
     7     BinaryTreeNode *p=pRoot;   //二叉树分左右,所以光有栈不行,合理的运用遍历指针是关键之一
     8     while(p!=NULL)
     9     {
    10         visit(p);
    11         if (p->m_pRight!=NULL)
    12             S.push(p->m_pRight);
    13         if (p->m_pLeft!=NULL)
    14             p=p->m_pLeft;
    15         else
    16         {
    17             if (S.empty())
    18                 break;
    19             p=S.top();
    20             S.pop();
    21         }
    22     }
    23 }  

2、中序遍历的非递归实现

根据中序遍历的顺序,先访问左子树,再访问根节点,后访问右子树,而对于每个子树来说,又按照同样的访问顺序进行遍历,上图的中序遍历顺序为:DBEAFC。非递归的实现思路如下:

对于任一节点P,

1)若P的左孩子不为空,则将P入栈并将P的左孩子置为当前节点,然后再对当前节点进行相同的处理;

2)若P的左孩子为空,则输出P节点,而后将P的右孩子置为当前节点,看其是否为空;

3)若不为空,则重复1)和2)的操作;

4)若为空,则执行出栈操作,输出栈顶节点,并将出栈的节点的右孩子置为当前节点,看起是否为空,重复3)和4)的操作;

5)直到当前节点P为NULL并且栈为空,则遍历结束。

   下面以上图为例详细分析其中序遍历的非递归实现过程:

首先,从根节点A开始,A的左孩子不为空,根据操作1)将A入栈,接着将B置为当前节点,B的左孩子也不为空,根据操作1),将B也入栈,接着将D置为当前节点,由于D的左子树为空,根据操作2),输出D;

由于D的右孩子也为空,根据操作4),执行出栈操作,将栈顶结点B出栈,并将B置为当前节点,此时输出序列为DB;

由于B的右孩子不为空,根据操作3),将其右孩子E置为当前节点,由于E的左孩子为空,根据操作1),输出E,此时输出序列为DBE;

由于E的右孩子为空,根据操作4),执行出栈操作,将栈顶节点A出栈,并将节点A置为当前节点,此时输出序列为DBEA;

此时栈为空,但当前节点A的右孩子并不为NULL,继续执行,由于A的右孩子不为空,根据操作3),将其右孩子C置为当前节点,由于C的左孩子不为空,根据操作1),将C入栈,将其左孩子F置为当前节点,由于F的左孩子为空,根据操作2),输出F,此时输出序列为:DBEAF;

由于F的右孩子也为空,根据操作4),执行出栈操作,将栈顶元素C出栈,并将其置为当前节点,此时的输出序列为:DBEAFC;

由于C的右孩子为NULL,且此时栈空,根据操作5),遍历结束。

根据以上思路,中序遍历的非递归实现代码如下:

[cpp] view plain copy

  1.  1 void in_traverse(BTree pTree)
     2 {
     3     PSTACK stack = create_stack();  //创建一个空栈
     4     BTree node_pop;                 //用来保存出栈节点
     5     BTree pCur = pTree;             //定义指向当前访问的节点的指针
     6
     7     //直到当前节点pCur为NULL且栈空时,循环结束
     8     while(pCur || !is_empty(stack))
     9     {
    10         if(pCur->pLchild)
    11         {
    12             //如果pCur的左孩子不为空,则将其入栈,并置其左孩子为当前节点
    13             push_stack(stack,pCur);
    14             pCur = pCur->pLchild;
    15         }
    16         else
    17         {
    18             //如果pCur的左孩子为空,则输出pCur节点,并将其右孩子设为当前节点,看其是否为空
    19             printf("%c ", pCur->data);
    20             pCur = pCur->pRchild;
    21             //如果为空,且栈不空,则将栈顶节点出栈,并输出该节点,
    22             //同时将它的右孩子设为当前节点,继续判断,直到当前节点不为空
    23             while(!pCur && !is_empty(stack))
    24             {
    25                 pCur = getTop(stack);
    26                 printf("%c ",pCur->data);
    27                 pop_stack(stack,&node_pop);
    28                 pCur = pCur->pRchild;
    29             }
    30         }
    31     }
    32 }  

    另一种思路

     1 #include<stack>
     2 void InOrder(BinaryTreeNode* pRoot)
     3 {
     4     if (pRoot==NULL)
     5         return;
     6     std::stack<BinaryTreeNode*> S;
     7     BinaryTreeNode *p=pRoot;
     8     do
     9     {
    10         while(p!=NULL)
    11         {
    12             S.push(p);
    13             p->m_pLeft;
    14         }
    15         //若进行到这里左子树为空
    16         if (!S.empty())//Stack不空时退栈,然后访问该元素
    17         {
    18             p=S.top();
    19             S.pop();
    20             visit(p);
    21             p=p->m_pRight;
    22         }
    23     } while (p!=NULL||!S.empty());
    24     //这里的p==NULL表示右子树为空,然后堆栈如果也空的话,才是处理完毕
    25 }  

3、后序遍历的非递归实现

根据后序遍历的顺序,先访问左子树,再访问右子树,后访问根节点,而对于每个子树来说,又按照同样的访问顺序进行遍历,上图的后序遍历顺序为:DEBFCA。后序遍历的非递归的实现相对来说要难一些,要保证根节点在左子树和右子树被访问后才能访问,思路如下:

对于任一节点P,

1)先将节点P入栈;

2)若P不存在左孩子和右孩子,或者P存在左孩子或右孩子,但左右孩子已经被输出,则可以直接输出节点P,并将其出栈,将出栈节点P标记为上一个输出的节点,再将此时的栈顶结点设为当前节点;

3)若不满足2)中的条件,则将P的右孩子和左孩子依次入栈,当前节点重新置为栈顶结点,之后重复操作2);

4)直到栈空,遍历结束。

   下面以上图为例详细分析其后序遍历的非递归实现过程:

首先,设置两个指针:Cur指针指向当前访问的节点,它一直指向栈顶节点,每次出栈一个节点后,将其重新置为栈顶结点,Pre节点指向上一个访问的节点;

Cur首先指向根节点A,Pre先设为NULL,由于A存在左孩子和右孩子,根据操作3),先将右孩子C入栈,再将左孩子B入栈,Cur改为指向栈顶结点B;

由于B的也有左孩子和右孩子,根据操作3),将E、D依次入栈,Cur改为指向栈顶结点D;

由于D没有左孩子,也没有右孩子,根据操作2),直接输出D,并将其出栈,将Pre指向D,Cur指向栈顶结点E,此时输出序列为:D;

由于E也没有左右孩子,根据操作2),输出E,并将其出栈,将Pre指向E,Cur指向栈顶结点B,此时输出序列为:DE;

由于B的左右孩子已经被输出,即满足条件Pre==Cur->lchild或Pre==Cur->rchild,根据操作2),输出B,并将其出栈,将Pre指向B,Cur指向栈顶结点C,此时输出序列为:DEB;

由于C有左孩子,根据操作3),将其入栈,Cur指向栈顶节点F;

由于F没有左右孩子,根据操作2),输出F,并将其出栈,将Pre指向F,Cur指向栈顶结点C,此时输出序列为:DEBF;

由于C的左孩子已经被输出,即满足Pre==Cur->lchild,根据操作2),输出C,并将其出栈,将Pre指向C,Cur指向栈顶结点A,此时输出序列为:DEBFC;

由于A的左右孩子已经被输出,根据操作2),输出A,并将其出栈,此时输出序列为:DEBFCA;

此时栈空,遍历结束。

根据以上思路,后序遍历的非递归实现代码如下:

[cpp] view plain copy

  1.  1 void beh_traverse(BTree pTree)
     2 {
     3     PSTACK stack = create_stack();  //创建一个空栈
     4     BTree node_pop;          //用来保存出栈的节点
     5     BTree pCur;              //定义指针,指向当前节点
     6     BTree pPre = NULL;       //定义指针,指向上一各访问的节点
     7
     8     //先将树的根节点入栈
     9     push_stack(stack,pTree);
    10     //直到栈空时,结束循环
    11     while(!is_empty(stack))
    12     {
    13         pCur = getTop(stack);   //当前节点置为栈顶节点
    14         if((pCur->pLchild==NULL && pCur->pRchild==NULL) ||
    15             (pPre!=NULL && (pCur->pLchild==pPre || pCur->pRchild==pPre)))
    16         {
    17             //如果当前节点没有左右孩子,或者有左孩子或有孩子,但已经被访问输出,
    18             //则直接输出该节点,将其出栈,将其设为上一个访问的节点
    19             printf("%c ", pCur->data);
    20             pop_stack(stack,&node_pop);
    21             pPre = pCur;
    22         }
    23         else
    24         {
    25             //如果不满足上面两种情况,则将其右孩子左孩子依次入栈
    26             if(pCur->pRchild != NULL)
    27                 push_stack(stack,pCur->pRchild);
    28             if(pCur->pLchild != NULL)
    29                 push_stack(stack,pCur->pLchild);
    30         }
    31     }
    32 }  

以上遍历算法在VC上实现的输出结果如下:

完整代码下载地址(自己的劳动成果,需要2个积分,大家多多谅解):http://download.csdn.net/detail/mmc_maodun/6445579

另外,关于二叉树层序遍历的实现思想及算法,请参见:http://blog.csdn.net/ns_code/article/details/13169703

1.4习题举例

输入一棵二叉搜索树,将该二叉搜索树转换为一个排序的双向链表。要求不能创建任何新的结点,只能调整树中指针的指向。

分析:因为是二叉搜索树所以转变的过程就是节点的左指针指向左子树的最大值,又指针指向右子树的最小值。可以用中序遍历的算法结构来编程。

 1 BinaryTreeNode* Convert(BinaryTreeNode* pRootOfTree)
 2 {
 3 if(pRootOfTree==NULL)
 4     return NULL;
 5     BinaryTreeNode *pLastNodeInList = NULL;
 6     ConvertNode(pRootOfTree, &pLastNodeInList);
 7     // pLastNodeInList指向双向链表的尾结点,
 8     // 我们需要返回头结点
 9     BinaryTreeNode *pHeadOfList = pLastNodeInList;
10     while(pHeadOfList != NULL && pHeadOfList->m_pLeft != NULL)
11         pHeadOfList = pHeadOfList->m_pLeft;
12     return pHeadOfList;
13 }
14
15 void ConvertNode(BinaryTreeNode* pNode, BinaryTreeNode** pLastNodeInList)
16 {
17     if(pNode == NULL)
18         return;
19     BinaryTreeNode *pCurrent = pNode;
20     //visit_begin
21     if (pCurrent->m_pLeft != NULL)
22         ConvertNode(pCurrent->m_pLeft, pLastNodeInList);
23     pCurrent->m_pLeft = *pLastNodeInList;
24     if(*pLastNodeInList != NULL)
25         (*pLastNodeInList)->m_pRight = pCurrent;
26     *pLastNodeInList = pCurrent;
27     //visit_end
28     if (pCurrent->m_pRight != NULL)
29         ConvertNode(pCurrent->m_pRight, pLastNodeInList);
30 }  
时间: 2024-10-02 16:30:31

【数据结构与算法】二叉树递归与非递归遍历(附完整源码)(转)的相关文章

简单算法和简单逻辑的小软件,是如何获得技术专利的?附完整源码

源码在最后面有下载,是入行一年时的项目.虽然简陋,却是我人生中的重要里程碑.一直想把背后的故事讲出来,但代码长得丑陋不好意思拿出来. 眼见年终了,丑代码放出来图大家一乐,如果有人学到东西或被后面的故事激起斗志,更是功德一件. 功能介绍:一个大片(图中m1-m4四个红色标记点组成),指定旋转角度,最多能切割成多少黑灰色的小片. 输入:见图,小片宽高,大片宽高,角度. 输出:最大切片数. 技术涉及屏幕坐标系,数学和几何计算,画图,多线程. 完整的项目有串口控制通信和一些实际工程的代码,和硬件绑定在一

小甲鱼数据结构和算法-----二叉树的构建和前序遍历

题目要求:建立二叉树并输出每个字符所在的层数.如下图要求输出 A 在第一层 B.C 在第二层 D.E在第三层 代码如下: #include <stdio.h> #include <stdlib.h> typedef struct BiTNode { char data; struct BiTNode *lchild,*rchild; }BiTNode,*BiTree; // 创建一棵二叉树,约定用户遵照前序遍历的方式输入数据 void CreateBiTree(BiTree *T)

【数据结构与算法】内部排序之一:插入排序和希尔排序的N中实现(不断优化,附完整源码)

转载请注明出处:http://blog.csdn.net/ns_code/article/details/20043459   前言 本来想将所有的内部排序总结为一篇博文,但是随着研究的深入,还是放弃了这个念头,斟前酌后,还是觉得分开来写比较好,具体原因,看完本篇博文也就自然明了了. 本篇文章主要探讨插入排序和希尔排序,之所将二者放在一起,很明显,是因为希尔排序是建立在插入排序的基础之上的.     注:以下各排序算法的N种实现方法大部分都是我根据算法思想,自己写出来的,或者是参考其本身的经典实

数据结构二叉树的递归与非递归遍历之 实现可编译(1)java

前一段时间,学习数据结构的各种算法,概念不难理解,只是被C++的指针给弄的犯糊涂,于是用java,web,javascript,分别去实现数据结构的各种算法. 二叉树的遍历,本分享只是以二叉树中的先序遍历为例进行说明,中序遍历和后序遍历,以此类推! 二叉树递归与非递归遍历的区别,虽然递归遍历,跟容易读懂,代码量少,运算快,但是却容易出现溢出的问题,所以所以非递归遍历,在处理千万级的运算量时会先的很有用处. 二叉树的先序遍历:先访问根节点,再访问先后访问左右节点.如图: 二叉树的递归遍历之java

二叉树遍历算法总结(递归与非递归)

一:前言 二叉树的遍历方法分四种:前序,中序,后序以及层次遍历. 其中,前中后遍历方法的实现分递归和非递归,非递归遍历的实现需要借助于栈. 实际上,递归的调用就是一种栈的实现,所以,非递归遍历就需要人工借助栈结构来实现. 而层次遍历需要借助队列. 二:前中后序遍历 递归遍历: 递归遍历的思想和方法很简单,通过调整输出语句来实现前,中,后三种遍历. 代码如下: 1 void show(BiTree T) 2 { 3 if(T) 4 { 5 printf("%c ",T->data)

二叉树递归与非递归遍历,最近公共父节点算法

#include <iostream> #include <stack> using namespace std; #define MAX 100 //字符串最大长度 typedef struct Node //二叉树结点 { char data; Node *lchild,*rchild; } *Btree; void createBT(Btree &t); //先序构造二叉树 void preorder(Btree &t); //二叉树递归先序遍历 void i

每天刷个算法题20160521:二叉树高度(递归与非递归)

版权所有.所有权利保留. 欢迎转载,转载时请注明出处: http://blog.csdn.net/xiaofei_it/article/details/51502727 为了防止思维僵化,每天刷个算法题.已经刷了几天了,现在发点代码. 我已经建了一个开源项目,每天的题目都在里面: https://github.com/Xiaofei-it/Algorithms 绝大部分算法都是我自己写的,没有参考网上通用代码.读者可能会觉得有的代码晦涩难懂,因为那是我自己的理解. 最近几天都是在写一些原来的东西

每天刷个算法题20160518:非递归二叉树遍历

版权所有.所有权利保留. 欢迎转载,转载时请注明出处: http://blog.csdn.net/xiaofei_it/article/details/51502254 为了防止思维僵化,每天刷个算法题.已经刷了几天了,现在贴点代码. 2002年我初中二年级,开始学习BASIC语言.2004年中考之后,开始参加NOIP,系统学习算法.一直非常喜欢算法,但工作后几乎不再碰这些东西.现在准备重新捡起来. 我已经建了一个开源项目,每天的题目都在里面: https://github.com/Xiaofe

重拾算法(1)——优雅地非递归遍历二叉树及其它

重拾算法(1)——优雅地非递归遍历二叉树及其它 本文中非递归遍历二叉树的思想和代码都来自这里(http://jianshu.io/p/49c8cfd07410#).我认为其思想和代码都足够优雅动人了,于是稍作整理,得到如下的程序. 前中后序遍历二叉树 1 public class BinaryTreeNode<T> 2 { 3 public T Value { get;set; } 4 public BinaryTreeNode<T> Parent { get;set; } 5 p