数据结构:树的遍历

树的遍历规则

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

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

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

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

创建一棵二叉树

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

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的左右子树遍历完了,函数栈帧销毁,先序遍历函数调用结束。

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

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

中序遍历

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

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

后序遍历

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

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

相关推荐
hold?fish:palm10 小时前
24 回文链表
数据结构·c++·链表
qeen8714 小时前
【数据结构】自平衡二叉搜索树各种旋转算法原理解析及AVL树的C++实现
数据结构·c++·算法
冻柠檬飞冰走茶15 小时前
PTA基础编程题目集 7-35有理数均值(C++语言实现)
开发语言·数据结构·c++·算法·均值算法
Nil20816 小时前
leetcode 189轮转数组
数据结构·算法·leetcode
依然鸣17 小时前
PTA团体程序设计天梯赛L1真题讲解L1-085-088
数据结构·c++·经验分享·算法·深度优先·pat考试
Brilliantwxx18 小时前
【算法从零到千】【51-54】逆序对(分治+递归算法)
数据结构·算法·排序算法
小星星闪亮登场18 小时前
2026河南萌新联赛第四场--南阳理工学院
数据结构·算法·贪心算法·动态规划·哈希算法·广度优先
土司大王18 小时前
LeetCode hot100——两数之和
数据结构·算法·leetcode
wabs66619 小时前
关于字符串【力扣344.反转字符串的思考】
数据结构·算法·leetcode
LuminousCPP19 小时前
数据结构基础篇(二):顺序表与链表全方位对比|从内存布局到 CPU 缓存理解底层差异
c语言·数据结构·经验分享·链表·缓存