数据结构(15.1)二叉树的遍历及其七种实现方式

本文介绍了二叉树的四种遍历方法:先序、中序、后序和层次遍历,分别给出了递归和非递归的实现方式。在先序遍历中,先访问根节点,再遍历左子树和右子树。中序遍历则是先遍历左子树,然后访问根节点,再遍历右子树。后序遍历则是先遍历左右子树,最后访问根节点。层次遍历按照从上至下、从左至右的顺序访问节点。递归方法通过函数调用实现,非递归方法利用栈或队列的数据结构进行遍历。
摘要由CSDN通过智能技术生成

前言

最近看到二叉树,因为二叉树的内容比较多,就把遍历单独总结成一篇博客,其他内容后面再写。

首先明确一下遍历的意义:二叉树有根结点、左子树和右子树三个部分,若能按一定的顺序访问这三个部分,就是遍历了整个二叉树。假如访问的先后顺序可以随机,一共会有六种可能性。

现在我们规定,只能先访问左子树,再访问右子树,就只剩下三种情况:根结点->左子树->右子树,左子树->根结点->右子树, 左子树->右子树->根结点。

这就是二叉树的先序、中序、后序遍历,这个所谓的 “序” 就是指根结点的访问次序。 “访问” 指的是对结点真正进行操作,例如输出结点数据、修改结点信息等。因为规定了访问的次序,可能会出现我们到达了某个结点,但是不进行任何操作,等下次到达再操作的情况。

注意,这个 “根结点” 是相对的概念,例如对下图的B结点而言,它既是A结点的左子树,也是C、D结点的根结点。也就是说,假设我们使用中序遍历,找到A的左子树B结点;由于B结点同时也是根结点,因此不能直接访问,而是接着寻找到B的左子树C。

img_1

同时,对二叉树的遍历也可以按从上至下、从左至右来进行。这样我们是 “一层一层” 地来访问二叉树的结点,称为层次遍历。

三种按次序的遍历都有递归和非递归两种实现方式,再加上层次遍历(本身就是非递归的),因此一共有七种实现遍历二叉树的方法。

先序遍历

先序遍历二叉树的操作是:若二叉树为空,则结束遍历;否则先访问根结点,然后先序遍历左子树,再先序遍历右子树。(左右子树同样是二叉树,遍历时也按照先序遍历的方式进行。)

递归方法

用递归方法实现先序遍历非常简单,因为对左右子树同样采取先序遍历的方式,只需要调用同一个方法即可。

//先序遍历-递归
void PreOrder(BinTreeNode *t){
   
    if (t != NULL) {
   
        printf("%4c",t->data);
        PreOrder(t->leftChild);
        PreOrder(t->rightChild);
    }
}

我们来看看代码执行时的状况:

img_2

  1. 参数传入根结点A,A不为空,输出A结点的数据,再次调用先序遍历方法,传入A的左子树B

  2. 参数传入根结点B,B不为空,输出B结点的数据,再次调用先序遍历方法,传入B的左子树C

  3. 参数传入根结点C,C不为空,输出C结点的数据,再次调用先序遍历方法,传入C的左子树NULL

img_3

  1. 参数传入NULL,if 语句不执行,该方法结束。

  2. 再次调用先序遍历方法,传入C的右子树NULL

  3. 参数传入NULL,if 语句不执行,该方法结束。此时,③处的方法也执行完毕了,回到②处

    img_4

  4. 再次调用先序遍历方法,传入B的右子树D

  5. 参数传入根结点D,D不为空,输出D结点的数据,再次调用先序遍历方法,传入D的左子树E

  6. 参数传入根结点E, E不为空,输出E结点的数据, 再次调用先序遍历方法,传入E的左子树NULL

  7. 同4~6,左右子树为空,不执行语句,⑤处方法执行完毕,回到④处,传入D的右子树

  8. 参数传入根结点F, F不为空,输出F结点的数据, 再次调用先序遍历方法,传入F的左子树

  9. 同4~6,左右子树为空,不执行语句,⑥处方法执行完毕,同时,④处、②处方法也执行完毕

img_5

  1. 回到①处,传入A的右子树G,后续的照推即可。
非递归方法

我们知道,递归方法是可以转换为非递归方法的。观察上面的递归过程,不难发现一点:递归方法的处理顺序,是遵循先入后出规律的。最先执行的A结点方法,是在最后被处理完的。而 “先入后出” 一词很容易让我们想到栈结构。

实际上在计算机中,函数(方法)的调用就是通过栈来实现的,递归函数自然也不例外。只是,系统所维护的这个函数栈是有一定大小的,因此递归函数隐含着一个问题:递归的次数过多,会造成栈的溢出。

我们想将递归方法转为非递归方法,也离不开栈结构,不过这个栈结构不是系统设计的,而是我们自己设计维护的。这里使用的栈结构是之前写好的顺序栈,见之前的博客栈结构之顺序栈

在之前的顺序栈中,结点的类型是int型,因此要修改一下才能保存二叉树的结点。

struct BinTreeNode;
#define EType BinTreeNode*
typedef struct SeqStack{
   
    struct EType *base;
    //栈容量
    int capacity;
    //表示栈顶所在的位置,也表示了当前栈内元素的个数
    int top;
}SeqStack;

修改完之后就可以直接调用之前写好的方法了。现在考虑:怎么用栈结构实现先序遍历的非递归?

img_6

其实,利用栈结构对二叉树进行遍历,无非就是一个个结点先入栈再出栈的过程。每个结点出栈时,执行对结点的 访问操作,这样来输出遍历序列(出栈之前,通过获取栈顶元素的方法来得到即将出栈的结点,就可以实现访问了)。而出栈的顺序是由入栈的顺序决定的,也就是说,最后输出的序列是先序、中序还是后序,重点在入栈的顺序。

下面看先序遍历时的入栈顺序。需要明确:

  1. 整棵树的根结点必然先入栈
  2. 因为是先访问左子树,再访问右子树,意味着左子树先出栈,右子树后出栈,因此入栈时先右后左
  3. 空结点不入栈

代码如下:

//先序遍历-非递归
void PreOrder_2(BinTreeNode *t){
   
    if (t != NULL) {
   
        SeqStack Q;
        InitStack(&Q);
        
        //根结点入栈
        Push(&Q, t);
        
        BinTreeNode *s
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值