二叉树的遍历:先序中序后序遍历的递归与非递归实现及层序遍历

  对于一种数据结构而言,遍历是常见操作。二叉树是一种基本的数据结构,是一种每个节点的儿子数目都不多于2的树。二叉树的节点声明如下:

1 typedef struct TreeNode *PtrToNode;
2 typedef struct TreeNode *BinTree;
3
4 struct TreeNode
5 {
6     int Data;    //为简单起见,不妨假设树节点的元素为int型
7     BinTree Left;
8     BinTree Right;
9 };

  二叉树的遍历主要有先序遍历,中序遍历,后序遍历,层序遍历四种方式,下面一一介绍。

  1. 先序遍历

  在先序遍历中,对节点的访问工作是在它的左右儿子被访问之前进行的。换言之,先序遍历访问节点的顺序是根节点-左儿子-右儿子。由于树可以通过递归来定义,所以树的常见操作用递归实现常常是方便清晰的。递归实现的代码如下:

1 void PreOrderTraversal(BinTree BT)
2 {
3     if( BT )
4     {
5         printf(“%d\n”, BT->Data);        //对节点做些访问比如打印
6         PreOrderTraversal(BT->Left);     //访问左儿子
7         PreOrderTraversal(BT->Right);    //访问右儿子
8     }
9 }

  由递归代码可以看出,该递归为尾递归(尾递归即递归形式在函数末尾或者说在函数即将返回前)。尾递归的递归调用需要用栈存储调用的信息,当数据规模较大时容易越出栈空间。虽然现在大部分的编译器能够自动去除尾递归,但是即使如此,我们不妨自己去除。非递归先序遍历算法基本思路:使用堆栈

  a. 遇到一个节点,访问它,然后把它压栈,并去遍历它的左子树;

  b. 当左子树遍历结束后,从栈顶弹出该节点并将其指向右儿子,继续a步骤;

  c. 当所有节点访问完即最后访问的树节点为空且栈空时,停止。

  实现代码如下:

void PreOrderTraversal(BinTree BT)
{
    BinTree T = BT;
    Stack S = CreatStack(MAX_SIZE);    //创建并初始化堆栈S
    while(T || !IsEmpty(S))
    {
        while(T)        //一直向左并将沿途节点访问(打印)后压入堆栈
        {
            printf("%d\n", T->Data);
            Push(S, T);
            T = T->Left;
        }
        if (!IsEmpty(S))
        {
            T = Pop(S);    //节点弹出堆栈
            T = T->Right;  //转向右子树
        }
    }}

  2. 中序遍历

  中序遍历的遍历路径与先序遍历完全一样。其实现的思路也与先序遍历非常相似。其主要的不同点是访问节点顺序不同:中序遍历是访问完所有左儿子后再访问根节点,最后访问右儿子,即为左儿子-根节点-右儿子。

  递归实现的代码如下:

void InOrderTraversal(BinTree BT)
{
    if(BT)
    {
        InOrderTraversal(BT->Left);
        printf("%d\n", BT->Data);
        InOrderTraversal(BT->Right);
    }
}

  非递归实现代码如下:

void InOrderTraversal(BinTree BT)
{
    BinTree T = BT;
    Stack S = CreatStack(MaxSize); //创建并初始化堆栈S
    while(T || !IsEmpty(S))  {
      while(T)    //一直向左并将沿途节点压入堆栈
      {
          Push(S,T);
          T = T->Left;
      }
      if(!IsEmpty(S))
      {
          T = Pop(S);                //节点弹出堆栈
          printf("%d\n", T->Data);    //(访问) 打印结点
          T = T->Right;              //转向右子树
      }  }
}  

  3. 后序遍历

  后序遍历与中序遍历,先序遍历的路径也完全一样。主要的不同点是后序遍历访问节点的顺序是先访问左儿子和右儿子,最后访问节点,即左儿子-右儿子-根节点。

  递归实现思路与中序遍历和先序遍历相似,代码如下:

void PostOrderTraversal(BinTree BT)
{
    if (BT)
    {
        PostOrderTraversal(BT->Left);
        PostOrderTraversal(BT->Right);
        printf("%d\n", BT->Data);
    }
}

  后序遍历的非递归实现

  思路一:

    对于一个节点而言,要实现访问顺序为左儿子-右儿子-根节点,可以利用后进先出的栈,在节点不为空的前提下,依次将根节点,右儿子,左儿子压栈。故我们需要按照根节点-右儿子-左儿子的顺序遍历树,而我们已经知道先序遍历的顺序是根节点-左儿子-右儿子,故只需将先序遍历的左右调换并把访问方式打印改为压入另一个栈即可。最后一起打印栈中的元素。代码如下:

void PostOrderTraversal(BinTree BT)
{
    BinTree T = BT;
    Stack S1 = CreatStack(MAX_SIZE);    //创建并初始化堆栈S1
    Stack S2 = CreatStack(MAX_SIZE);    //创建并初始化堆栈S2
    while(T || !IsEmpty(S1))
    {
        while(T)        //一直向右并将沿途节点访问(压入S2)后压入堆栈S1
        {
            Push(S2, T);
            Push(S1, T);
            T = T->Right;
        }
        if (!IsEmpty(S1))
        {
            T = Pop(S1);    //节点弹出堆栈
            T = T->Left;  //转向左子树
        }
    }
    while(!IsEmpty(S2))    //访问(打印)S2中元素
    {
        T = Pop(S2);
        printf("%d\n", T->Data);
    }
}

    思路一的优点是由于利用了先序遍历的思想,代码较简洁,思路较清晰。缺点是需要用一个栈来存储树的所有节点,空间占用较大。  

  思路二:

    要访问一个节点的条件上一个访问的节点是右儿子。我们可以增加一个变量Prev来判断当前节点Curr的上一个节点与它的关系来执行相应的操作。

  •     若Prev为空(Curr节点是根节点)或者Prev是Curr的父节点,将Curr节点的左孩子和右孩子分别压入栈;
  •     若Prev是Curr的左儿子,则将Curr的右儿子压入栈;
  •     否则Prev是Curr的右儿子,访问Curr;

    代码如下:

void PostOrderTraversal(BinTree BT)
{
    if(BT == NULL)
        return ;
    Stack S = CreatStack(MAX_SIZE);
    BinTree Prev = NULL , Curr = NULL;     //初始化
    s.push(BT);
    while(!IsEmpty(S))
    {
        Curr = Top(S);        //将栈顶元素赋给Curr
        if(Prev == NULL  || Prev->Left == Curr || Prev->Right == Curr) //若Prev为NULL或是Curr的父节点
        {
            if(Curr->Left != NULL)
                Push(S, Curr->Left);
            else if(Curr->Right != NULL)
                Push(S, Curr->Right);
        }
        else if(Curr->Left == Prev)    //若Prev是Curr的左儿子
        {
            if(Curr->Right != NULL)
                Push(S, Curr->Right);
        }
        else
        {
            printf("%d\n", Curr->Data);    //访问当前节点
            Pop(S);    //访问后弹出
        }
        Prev = Curr;    //处理完当前节点后将Curr节点变为Prev节点
    }
}

  4. 层序遍历

  二叉树遍历的核心问题是二维结构的线性化。我们通过节点访问其左右儿子时,存在的问题是访问左儿子后,右儿子怎么访问。因此我们需要一个存储结构保存暂时不访问的节点。前面三种遍历方式的非递归实现,我们是通过堆栈来保存。事实上也可以通过队列来保存。

  队列实现的基本思路:遍历从根节点开始,首先将根节点入队,然后执行循环:节点出队,访问(访问)根节点,将左儿子入队,将右儿子入队,直到队列为空停止。

  这种遍历方式的结果是将二叉树从上到下,从左至右一层一层的遍历,即层序遍历,代码实现如下:

void LevelOrderTraversal(BinTree BT)
{
    BinTree T;
    Queue Q;    //声明一个队列
    if (BT == NULL)
        return;                 //如果树为空,直接返回
    Q = CreatQueue(MAX_SIZE);   //创建并初始化队列
    AddQ(Q, BT);                //将根节点入队
    while (!IsEmpty(Q))
    {
        T = DeleteQ(Q);              //节点出队
        printf("%d\n", T->Data);         //访问出队的节点
        if (T->Left)    AddQ(Q, T->Left);   //若左儿子不为空,将其入队
        if (T->Right)    AddQ(Q, T->Right)  //若右儿子不为空,将其入队
    }
}

理解上述四种二叉树遍历方式后,不妨来小试牛刀:List Leaves, Tree Traversals Again.

时间: 2024-11-05 18:34:28

二叉树的遍历:先序中序后序遍历的递归与非递归实现及层序遍历的相关文章

二叉树的前序、中序、后序遍历(递归、非递归)实现

本文部分来源于CSDN兰亭风雨大牛的原创.链接为http://blog.csdn.net/ns_code/article/details/12977901 二叉树是一种非常重要的数据结构,很多其他数据机构都是基于二叉树的基础演变过来的.二叉树有前.中.后三种遍历方式,因为树的本身就是用递归定义的,因此采用递归的方法实现三种遍历,不仅代码简洁且容易理解,但其开销也比较大,而若采用非递归方法实现三种遍历,则要用栈来模拟实现(递归也是用栈实现的).下面先简要介绍三种遍历方式的递归实现,再详细介绍三种遍

二叉树的先序、中序、后序、层次遍历的递归和非递归解法

二叉树的先序.中序.后序.层次遍历的递归和非递归解法 package tree; import java.util.LinkedList; import java.util.Queue; import java.util.Stack; public class TreeTraverse { /** * 先序递归 * @param root */ public static void preOrderTraverse(TreeNode root) { if (root == null) { ret

给定二叉树的先序遍历和中序遍历,输出它的后序遍历序列

这里没再用到先申请大Node数组的方法了,想练练写动态内存分配和释放的,一次OK了,也没怎么出错啊,开心~ 方法二 - Code: //给出一个二叉树的先序遍历和中序遍历,输出它的后序遍历 //直接构造的方法白书已给出.这里是先递归构造二叉树,然后进行后序遍历. #include<stdio.h> #include<string.h> #include<stdlib.h> #define MAXN 1000 typedef struct node { char data

算法进化历程之“根据二叉树的先序和中序序列输出后序序列”

巧若拙(欢迎转载,但请注明出处:http://blog.csdn.net/qiaoruozhuo) 前不久在看到一个作业"根据二叉树的先序和中序序列输出后序序列",当时我参考<数据结构与算法(C语言)习题集>上的做法,先根据先中序序列确定一颗二叉树,然后后序遍历二叉树输出后序序列. 函数采用了递归算法,利用函数传入的先序和中序序列的左右边界,确定要处理的序列段,生成相应的二叉树. 基本思路是,把该段先序序列的第一个元素作为当前二叉树的根结点,然后在中序序列找到根结点.根结点

分别求二叉树前、中、后序的第k个节点

一.求二叉树的前序遍历中的第k个节点 //求先序遍历中的第k个节点的值 int n=1; elemType preNode(BTNode *root,int k){ if(root==NULL) return ' '; if(n==k) return root->data; n++; elemType ch = preNode(root->lchild,k); if(ch!=' ') return ch; ch = preNode(root->rchild,k); return ch;

通过二叉树的中序序列和后序序列获取前序序列

二叉树的遍历方式常见的三种是:先序遍历(ABC).中序遍历(BAC).后序遍历(BCA) 先序遍历: 若二叉树为空,则空操作:否则: 访问根结点; 先序遍历左子树: 先序遍历右子树. 中序遍历: 若二叉树为空,则空操作:否则: 中序遍历左子树: 访问根结点: 中序遍历右子树. 后序遍历: 若二叉树为空,则空操作:否则: 后序遍历左子树: 后序遍历右子树: 访问根结点. 在学习到 根据遍历序列确定二叉树 时,知道了:可以通过二叉树的先中或者中后遍历序列唯一确定一棵二叉树. 根据算法描述 使用jav

Java数据结构系列之——树(4):二叉树的中序遍历的递归与非递归实现

package tree.binarytree; import java.util.Stack; /** * 二叉树的中序遍历:递归与非递归实现 * * @author wl * */ public class BiTreeInOrder { // 中序遍历的递归实现 public static void biTreeInOrderByRecursion(BiTreeNode root) { if (root == null) { return; } biTreeInOrderByRecursi

Java数据结构系列之——树(5):二叉树的后序遍历的递归与非递归实现

package tree.binarytree; import java.util.Stack; /** * 二叉树后序遍历的递归与非递归实现 * * @author wl * */ public class BitreePostOrder { // 后序遍历的递归实现 public static void biTreePostOrderByRecursion(BiTreeNode root) { if (root == null) { return; } biTreePostOrderByRe

二叉树--(建树,前序,中序,后序)--递归和非递归实现

#include<iostream> #include<string.h> #include<stack> using namespace std; typedef struct BTree { int val; struct BTree *left,*right; }BTree; class Tree { public: BTree *create_node(int level,string pos); void PreOrder(BTree *t); //先序遍历

先序序列和后序序列并不能唯一确定二叉树

数据结构的基础知识中重要的一点就是能否根据两种不同遍历序列的组合(有三种:先序+中序,先序+后序,中序+后序),唯一的确定一棵二叉树.然后就是根据二叉树的不同遍历序列(先序.中序.后序),重构二叉树.显然,这三种组合并不是都能唯一确定二叉树的,其中先序+后序就不能唯一确定一棵二叉树,其他两种组合可以唯一的确定一颗二叉树. 由先序序列和后序序列不能唯一确定一棵二叉树,因无法确定左右子树两部分. 反例:任何结点只有左子树的二叉树和任何结点只有右子树的二叉树,其前序序列相同,后序序列相同,但却是两棵不