数据结构与算法(陈越版)第三讲 (树上)二叉树的遍历

树的表示

typedef struct TreeNode * BinTree;
struct TreeNode
{
    int Data;       // Stored value
    BinTree Left;   // 左二子Node
    BinTree Right;  // 右二子Node
};

一、二叉树的遍历

核心问题: 二维结构的线性化。

根据访问节点本使用的存储结构不同,分为栈(递归,递归本质就是栈的应用),队列遍历。

基于栈遍历

根据访问节点的时机不同分为三种遍历方式:

  1. 先序遍历:先序遍历是第一次"遇到"该结点时访问。
  2. 中序遍历:中序遍历是第二次"遇到"该结点(此时该结点从左子树返回)时访问
  3. 后序遍历:后序遍历是第三次"遇到"该结点(此时该结点从右子树返回)时访问
    在这里插入图片描述
1、先序遍历

递归过程

  1. 访问根节点
  2. 先序遍历二叉树的左子树
  3. 先序遍历二叉树的右子树
    先序遍历示意图
1.1、先序递归遍历算法
void  PreOrderTraversal(BinTree BT)
{
   if (BT)
   {
        pritntf("%5d", BT-> Data);              // 打印根节点
        PreOrderTraversal(BT-> Left);           // 进入左子树
        PreOrderTraversal(BT-> Right);          // 进入右子树
   }
}
1.2、先序非递归遍历算法
void PreOrderTraversal (BinTree BT)
{
   BinTree T = BT;
   Stack S = CreateStack(MaxSize);             // 创建并初始化堆栈S
   while(T || !IsEmpty(S))                     
   {
       while(T)
       {                                       // 一直向左并将沿途节点压入堆栈当中
           Push(S,T); 
           Printf("5d",T-> Data);
           T = T->Left;                        // 遍历左子树 
       }
       if(!IsEmpty(S))
       {
            T = Pop(s);
            T = T->Right;                      // 访问右结点
       }
   }
}
2、中序遍历

递归过程:

  1. 中序遍历二叉树左子树
  2. 访问根节点
  3. 中序遍历二叉树右子树

中序遍历示意图

2.1、中序递归遍历算法
void InOrderTraversal(BinTree BT)
{
    if(BT)
    {
        InOrderTraversal(BT->Left);
        Printf("%5d", BT-> Data);
        InOrderTraversal(BT->Right);    
    }
}
2.2、中序非递归遍历算法
void InOrderTraversal(BinTree BT)
{
   BinTree T = BT;
   Stack S = CreateStack(MaxSize);
   while( T || !IsEmpty(S))
   {
       while(T)
       {
           Push(S,T);
           T = T-> Left;
       }
       if(!IsEmpty(S))
       {
           T = Pop(S);
           Printf("5d", T-> Data);
           T = T-> Right;
       }
   }
}
3、后序遍历

递归过程:

  1. 后序遍历二叉树的左子树
  2. 后序遍历二叉树的右子树
  3. 访问根节点

后序遍历示意图

3.1、后序递归遍历算法
void PostOrderTraversal(BinTree BT)
{
    if(BT)
    {
        PostOrderTraversal(BT-> Left);
        PostOrderTraversal(BT-> Right);
        Printf("%5d", T->Data);
    }
}
3.2、后序非递归遍历算法

思考:后序遍历是第三次访问节点时,将节点数据打印。而上述前序中序均是访问1、2次,代码也是最多访问2次,因此不再适合后序遍历,需要将出栈的节点再次压栈。

方法一:

思考:记录每次节点的访问次数,访问到第三次就输出节点数据。

void PostOrderTraversal(BinTree BT)
{
    BinTree T = BT;
    Stack S = CreateStack(MaxSize);
    while(T || !IsEmpty(S))
    {  
        T->times = 1;       // 给树节点带一个出现次数空间,并首先置为1
        Push(S,T);   
        T = T-> Left;
        if(!IsEmpty(S))
        {
            T = Pop(S);     // 第二、三次遇到此节点
            if(T->times == 1)                 // 第二次遇到直接压入栈
            {
                 Push(S,T);
                 ++ T->Times;
                 T = T->Right;
            }
            elseif( T->Times == 2)
            {
                     Print("%5d", T-> Data);   // 第三次遇到打印
                     T = NULL;
            }
        }
    }
}

评价: 上述方式有一个明显缺点,就是占用空间多,树节点中还包括被访问次数。而且对于没有右儿子的节点无需再次压栈,运行时间也会变长。

方法一对应的树节点应该被从新定义:

typedef struct BinTree
{
        int Data;                      // 节点数据
        struct BinTree *Left;          // 左孩子指针
        struct BinTree *Right;         // 右孩子指针
        int times;                     // 被访问次数
}BinTree, *BinTree;

方法二: (出自慕课大佬的评论
思考:利用判断条件,判断出栈的节点是否需要再次被压栈。

void PostOrderTraversal(BinTree BT)
{ 
    BinTree T = BT; 
    BinTree temp = NULL;               // 保存上次出栈的节点,防止出栈之后无法访问
    Stack S = CreateStack(MaxSize);    // 创建栈
    while(T || !IsEmpty(S))
    {
        while(T)
        {
            Push(S,T); 
            T = T-> Left;
        }
        if(!IsEmpty(S))
        {
            T = Pos(S);
   // 若出栈的节点没有右儿子 或者 有右二子同时此节点上次出栈的节点就是自己右二子,则可以出栈。
            if(T-> Right == NULL || T-> Right == temp)
            {
               Printf("%5d", T-> Data);
               tempt = T;                 // 跟新上次保存此次出栈节点
               T = NULL;                  // 表示子树遍历玩,指针置空,后续将弹出其父节点
            }
            // 出栈节点有右节点同时上次出栈的并非其右节点,则需要再次压栈,方便访问其右节点数据
            else                       
            {
               Push(S,T);                 // 节点再次压栈
               T = T-> Right;             // 访问右节点
            }
        }   
    }   
}

基于队列遍历

层序遍历

遍历过程:从上至下,从左至右访问所有节点。

本质: 先根节点入队,然后:

  1. 从队列中取出一个元素;
  2. 访问该元素所指节点
  3. 若该元素所指节点的左、右儿子节点非空,则将其做左、右儿子的指针顺序入队
void LevelOrderTraversal(BinTree BT)
{
   Queue Q;  BinTree T;
   if(!BT) {return;}              // 若是空树则直接返回
   Q = CreateQueue(MaxSize);      // 创建并初始化队列Q
   AddQ(Q, BT);                   // 根节点入队
   while(!IsEmpty(Q))
   {
      T = DeleteQ(Q);
      Printf("%d\n", T-> Data);   // 访问出栈队列节点数据
      // 若出队节点有左右儿子,按顺序将节点左右儿子入队
      if(T-> Left) { AddQ(Q, T-> Left;)}
      elseif(T-> Right){ AddQ(Q, T-> Right;)}
   }
}

二、例题以及应用

例题1

例题1:遍历二叉树的应用:输出二叉树的叶子节点。
思考:只要在遍历节点的时候判断 “ 左右子树是否为空 ”

void PerOrderPrintLeaves(BinTree BT)
{
    if(BT)
    {
       if(!BT-> Left && BT-> Right)
       {
           printf("%d", BT-> Data);
       }
       PerOrderPrintLeaves(BT-> Left);
       PerOrderPrintLeaves(BT-> Right);
    }
}

例题二

例题二:求二叉树的高度
思考:从最底层出发。

  • [1] 当只有0层时,也就是空树。返回0,表示空二叉树高度为0。
  • [2] 当只有1层时。需要返回1,使用递归编程,空树作为基线条件。那么返回树的高度在基线返回值上加1。
  • [3 ]根据上述推导,因此树的高度就是左右子树最高者加1。
    在这里插入图片描述
int PostOrderGetHegint(BinTree BT)
{
    if(BT)
    {
	    int HL, HR, MaxH;
	    HL = PostOrderGetHegint(BT-> Left);
	    HR = PostOrderGetHegint(BT-> Right);
	    MaxH = (HL >= HR) ? HL : HR;
	    return (MaxH + 1);
	}
	else
	{
        return 0;
	}
}

例题三

例题三: 由两种遍历序列确定二叉树。(但是必须有中序遍历

后序序列: FDEBGCA 后序序列: FDBEACG 后序序列?

思考:

  • [1] 根据 后序遍历序列 最后一个节点 确定根节点。 如题:A为根节点
  • [2] 根据根节点在 中序序列中 分割左右两个子序列
  • [3] 对左右子树重复上述操作

答案:ABDFECG。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值