数据结构:树的遍历

树的遍历规则

树的遍历有三种方式,前序、中序还有后续遍历。

遍历方式 规则
前序/先序遍历 先遍历根结点,再遍历左子树,然后再遍历右子树
中序遍历 先遍历左子树,再遍历根结点,最后遍历右子树
后序遍历 先遍历左子树,再遍历右子树,最后遍历根结点

简单来说前序遍历、中序遍历、后续遍历的规则就是根左右、左根右、左右根。

之前我们用顺序表实现了堆结构,这是一种二叉树的结构,这里我们用链表结构(二叉链)来实现二叉树。

创建一棵二叉树

链表结构中二叉树是由一个个结点组成的,二叉树结点的结构如下:

c 复制代码
typedef char BTDataType;//类型重命名
//二叉树结点的结构
typedef struct BinaryTreeNode
{
     BTDataType data;//数据域
     struct BinaryTreeNode* left;//指向左孩子的指针
     struct BinaryTreeNode* right;//指向右孩子的指针
}BTNode;

然后我们可以封装一个函数BuyNode用来创建结点:

然后我们封装一个函数CreateBinaryTree来调用该函数创建多个结点,然后手动把这些结点链接起来形成一个二叉树。

创建出来的二叉树就是这样的:

前序遍历

前序遍历先遍历根结点,所以先打印A,然后遍历A的左子树,左子树以B为根结点,打印B,然后遍历B的左子树。B的左子树以D为根结点,打印D,然后遍历D的左子树。D的左子树以G为根结点,打印G,然后遍历G的左子树。G的左子树以NULL为根结点,说明遍历到头了,打印NULL(不打印也可以)。往上返,G的左子树遍历完后,遍历G的右子树。G的右子树以NULL为根结点,说明遍历到头了,打印NULL,往上返。D的左子树遍历完,遍历D的右子树,D的右子树以NULL为根结点,说明遍历到头了,打印NULL,往上返。B的左子树遍历完了,遍历B的右子树。B的右子树以NULL为根结点,打印NULL,往上返。A的左子树遍历完了,遍历A的右子树。A的右子树以C为根结点,打印C,再遍历C的左子树。C的左子树以E为根结点,打印E,再遍历E的左子树 。E的左子树以NULL为根结点,说明遍历到头了,打印NULL,往上返。E的左子树遍历完了,该遍历E的右子树。E的右子树以NULL为根结点,说明遍历到头了,打印NULL,往上返。C的左子树遍历完了,该遍历C的右子树,C的右子树以F为根结点,打印F,遍历F的左子树。F的左子树以NULL为根结点,说明遍历到头了,打印NULL,往上返。F的左子树遍历完了,该遍历F的右子树了。F的右子树以NULL为根结点,说明遍历到头了,打印NULL,往上返。C的左右子树遍历完了,再往上返。A的左右子树遍历完了,说明遍历结束。

这就是根左右的遍历顺序。

中序遍历和后续遍历无非就是改变遍历根结点的顺序,和上述分析过程一样,这里我就给出来了,可以自己遍历一遍试试。

我们之前学过,树的结构是递归的,那么树的遍历也应该是递归遍历的,接下来我们将体会递归的暴力美学。

前序遍历无非就是根左右的顺序,根root进来后要先判断root是否为空,为空则打印NULL,直接返回。我们要先打印根结点,然后再递归遍历根结点的左子树和右子树。

这样先序遍历的代码就完成了,是不是很简单?

我们打印一下:

与我们分析出来的结果一模一样,说明代码逻辑没问题,如果你对如何递归的存有好奇,没有关系,接下来我们将分析它是如何递归的?

递归分析

说到递归就要牵扯到函数栈帧了,我们知道每次调用函数都会创建函数栈帧,调用结束后会销毁函数栈帧,这里我们用实线代表函数栈帧创建的过程,虚线代表函数栈帧销毁的过程。

我们调用该函数,函数栈帧创建,传进去的根结点为A,A不为空,打印A,然后递归调用函数传A的左孩子B,函数栈帧创建:

B不为空,打印B,然后再调用该函数传B的左孩子,函数栈帧创建:

D不为空,打印D,然后调用函数传D的左孩子,函数栈帧创建:

G不为空,打印G,调用函数传G的左孩子过去,函数栈帧创建:

NULL为空,打印然后直接返回,函数栈帧销毁,往上返:

回到G的函数栈帧中,G的左子树遍历完成,调用函数传G的右孩子,函数栈帧创建:

NULL为空,打印然后直接返回,函数栈帧销毁,往上返,G的左右子树遍历完成,函数栈帧销毁,再往上返:

回到D的函数栈帧中,D的左子树遍历完成,调用函数传D的右孩子,函数栈帧创建:

NULL为空,打印然后直接返回,函数栈帧销毁,往上返,回到D的函数栈帧中,D的左右子树遍历完成,函数栈帧销毁,再往上返:

回到B的函数栈帧中,B的左子树遍历完成,调用函数传B的右孩子,函数栈帧创建:

NULL为空,打印然后直接返回,函数栈帧销毁,往上返回到B的函数栈帧中,B的左右子树遍历完成,函数栈帧销毁,再往上返:

回到A的函数栈帧中,A的左子树遍历完,调用函数传A的右孩子,函数栈帧创建:

C不为空,打印C,调用函数传C的左孩子,函数栈帧创建:

E不为空,打印E,调用函数传E的左孩子,函数栈帧创建:

NULL为空,打印然后直接返回,函数栈帧销毁,往上返,回到E的函数栈帧中,E的左子树遍历完,调用函数传E的右孩子,函数栈帧创建:

NULL为空,打印然后直接返回,函数栈帧销毁,往上返,回到E的函数栈帧中,E的左右子树遍历完成,函数栈帧销毁,再往上返:

回到C的函数栈帧中,C的左子树遍历完,调用函数传C的右孩子,函数栈帧创建:

F不为空,打印F,调用函数传F的左孩子,函数栈帧创建:

NULL为空,打印然后直接返回,函数栈帧销毁,往上返,回到F的函数栈帧中,F的左子树遍历完,调用函数传F的右孩子,函数栈帧创建:

NULL为空,打印然后直接返回,函数栈帧销毁,往上返,回到F的函数栈帧中,F的左右子树遍历完,函数栈帧销毁,再往上返:

回到C的函数栈帧中,C的右子树遍历完了,函数栈帧销毁,再往上返:

A的左右子树遍历完了,函数栈帧销毁,先序遍历函数调用结束。

整个递归过程化画出来就像一棵二叉树:

这就是整个函数递归的过程了。

中序遍历

中序遍历就是把根结点的打印放在中间就好了:

与预期结果一致,代码逻辑没什么问题。

后序遍历

后序遍历就是把根结点的打印放在最后就好了:

与预期结果一致,代码逻辑没什么问题。

相关推荐
j7~1 小时前
【数据结构初阶】队列的实现(链式队列 + 循环队列)--详解
c语言·开发语言·数据结构·学习·队列·queue·c\c++
king_linlin1 小时前
算法基础——算法复杂度
c语言·开发语言·数据结构·算法
king_linlin2 小时前
数据结构——顺序表(附图文讲解|超详细)
c语言·数据结构·算法
宵时待雨16 小时前
优选算法专题9:哈希表
数据结构·算法·散列表
txzrxz16 小时前
最短路问题——Dijkstra 算法
数据结构·c++·算法·最短路·优先队列
noipp16 小时前
推荐题目:洛谷 P6231 [JSOI2013] 公交系统
c语言·数据结构·c++·算法·游戏·洛谷·luogu
青山木1 天前
Hot 100 ---腐烂的橘子
java·数据结构·后端·算法·leetcode·广度优先
未来之窗软件服务1 天前
计算机考试-快速排序—东方仙盟
数据结构·算法·排序算法·仙盟创梦ide·东方仙盟
小龙报1 天前
【优选算法】1. 水果成蓝 2.找到字符串中所有字母的异位词
java·c语言·数据结构·数据库·c++·redis·算法