话不多说,直接上教程!下面是本篇目录。
前言:树和图都可以看作许多房间按照一定规则分布,每一个房间可以看作一个结点(树中称为结点,图中为顶点),小僧作为房东要访问每个房间,也就是遍历每个结点。无论房间如何分布,小僧总会按照一定的路线依次访问每个房间,也就是遍历方式不同。
二叉树的遍历
前序遍历(DLR)
- 递归算法思想
若二叉树为空则算法结束;否则:
(1)访问根结点;
(2)前序遍历根结点的左子树;
(3)前序遍历根结点的右子树。 - 通俗理解
如图1是简单的二叉树,我们定义A是B、C的根房间,B是A的左房间,C是A的右房间。小僧每访问一个房间之前,就要先访问它的根房间,再依次访问根房间的左房间、右房间。对于图1的二叉树,小僧依次访问房间A、B、C。
- 解题思路
按照这个逻辑,小僧每访问类似图1这样的简单二叉树都要遵循这个规则。对于图2复杂的二叉树,我们可以分析每个简单二叉树的访问顺序再汇总。表1为该二叉树的子二叉树及相应访问顺序。其中 A(_ B _ )( _ C _)表示确定ABC的访问顺序为ABC,但B、C及其左、右房间的访问次序需要进一步确定。
子二叉树 | 访问顺序 |
---|---|
A、B、C | A(_ B _ )( _ C _) |
B、D、E | B(_ D _ ) E |
D、G | DG |
C、F | C(_ F _ ) |
F、H | FH |
汇总后的前序遍历 | ABDGECFH |
看到这里是不是很神奇😄,小僧终于通过前序遍历把房间访问完了,咱们继续往下读~
中序遍历(LDR)
- 递归算法思想
若二叉树为空则算法结束;否则:
(1)中序遍历根结点的左子树;
(2)访问根结点;
(3)中序遍历根结点的右子树。 - 通俗理解
如图3是简单的二叉树,我们定义A是B、C的根房间,B是A的左房间,C是A的右房间。小僧每访问一个房间之前,就要先访问它的左房间,再依次访问这个房间(作为根房间)、右房间。对于图1的二叉树,小僧依次访问房间B、A、C。
- 解题思路
按照这个逻辑,小僧每访问类似图3这样的简单二叉树都要遵循这个规则。对于图4复杂的二叉树,我们可以分析每个简单二叉树的访问顺序再汇总。表2为该二叉树的子二叉树及相应访问顺序。其中 (_ B _ )A( _ C _)表示确定ABC的访问顺序为BAC,但B、C及其左、右房间的访问次序需要进一步确定。
子二叉树 | 访问顺序 |
---|---|
A、B、C | (_ B _ )A( _ C _) |
B、D、E | (_ D _ ) B E |
D、G | DG |
C、F | C (_ F _ ) |
F、H | HF |
汇总后的前序遍历 | DGBEACHF |
小僧终于中序遍历完了,咱们接着后序遍历😊~
后序遍历(LRD)
- 递归算法思想
若二叉树为空则算法结束;否则:
(1)后序遍历根结点的左子树;
(2)后序遍历根结点的右子树;
(3)访问根结点。 - 通俗理解
如图5是简单的二叉树,我们定义A是B、C的根房间,B是A的左房间,C是A的右房间。小僧每访问一个房间之前,就要先依次访问它的左、右房间,再访问它(作为根房间)。对于图5的二叉树,小僧依次访问房间B、C、A。
- 解题思路
按照这个逻辑,小僧每访问类似图5这样的简单二叉树都要遵循这个规则。对于图6复杂的二叉树,我们可以分析每个简单二叉树的访问顺序再汇总。表2为该二叉树的子二叉树及相应访问顺序。其中 (_ B _ )( _ C _)A表示确定ABC的访问顺序为BCA,但B、C及其左、右房间的访问次序需要进一步确定。
子二叉树 | 访问顺序 |
---|---|
A、B、C | (_ B _ )( _ C _) A |
B、D、E | (_ D _ ) EB |
D、G | GD |
C、F | (_ F _ )C |
F、H | HF |
汇总后的前序遍历 | GDEBHFCA |
小僧完成了后序遍历,接下来的层序遍历很简单呦😉
层序遍历
- 遍历算法
(1)初始化设置一个队列;
(2)把根结点指针入队列;
(3)当队列非空时,循环执行步骤(3.a)到步骤(3.c);
(3.a)出队列取得一个结点指针,访问该结点;
(3.b)若该结点的左子树非空,则将该结点的左子树指针入队列;
(3.c)若该结点的右子树非空,则将该结点的右子树指针入队列;
(4)结束。 - 通俗理解
按二叉树的层序次序(即从根结点层至叶结点层),同一层中按先左子树再右子树的次序遍历二叉树。这个是二叉树最好理解的遍历方式了。图7中二叉树的层序遍历为:ABCDEFGH。
在完成二叉树遍历的基础上,树的遍历就不难啦😎
树的遍历
先根遍历
- 递归算法思想
若树为空则算法结束;否则:
(1)访问根结点;
(2)按照从左到右的次序先根遍历根结点的每一棵子树。 - 通俗理解
如图8是简单的树,我们定义A是B、C、D的根房间,B、C、D是A的子房间。小僧每访问一个房间之前,就要先访问它的根房间,再按照从左到右的次序访问根房间的子房间。对于图8的树,小僧依次访问房间A、B、C、D。
- 解题思路
按照这个逻辑,小僧每访问类似图8这样的简单树都要遵循这个规则。对于图9复杂的树,我们可以分析每个简单的树访问顺序再汇总。表3为该树的子树及相应访问顺序。其中 A (_ B _ )( _ C _)( _ D _)表示确定ABC的访问顺序为ABCD,但B、C、D及其子房间的访问次序需要进一步确定。
子树 | 访问顺序 |
---|---|
A、B、C、D | A (_ B _ )( _ C _)( _ D _) |
B、E、F、G | BE(_ F _ )G |
F、J | FJ |
C、H | C(_ H _ ) |
H、K、L | HKL |
D、I | DI |
汇总后的先根遍历 | ABEFJGCHKLDI |
接下来后根遍历,👇
后根遍历
- 递归算法思想
若树为空则算法结束;否则:
(1)按照从左到右的次序后根遍历根结点的每一棵子树;
(2)访问根结点。 - 通俗理解
如图10是简单的树,我们定义A是B、C、D的根房间,B、C、D是A的子房间。小僧每访问一个房间之前,就要先按照从左到右的次序访问它的子房间,再访问它(作为根房间)。对于图10的树,小猿依次访问房间B、C、D、A。
- 解题思路
按照这个逻辑,小僧每访问类似图10这样的简单树都要遵循这个规则。对于图11复杂的树,我们可以分析每个简单的树访问顺序再汇总。表4为该树的子树及相应访问顺序。其中 (_ B _ )( _ C _)( _ D _)A表示确定ABCD的访问顺序为BCDA,但B、C、D及其子房间的访问次序需要进一步确定。
子树 | 访问顺序 |
---|---|
A、B、C、D | (_ B _ )( _ C _)( _ D _) A |
B、E、F、G | E(_ F _ )GB |
F、J | JF |
C、H | (_ H _ )C |
H、K、L | KLH |
D、I | ID |
汇总后的后根遍历 | EJFGEKLHCIDA |
当然,树还有层序遍历,类似于二叉树的层序遍历,这里不再给出,接下来进入图的遍历,👇
图的遍历
深度遍历(连通图)
- 递归算法思想
若图为空则算法结束;否则:
(1)访问顶点v并标记顶点v为已访问;
(2)查找顶点v的第一个邻接顶点w;
(3)若顶点v的邻接顶点w存在,则继续执行,否则算法结束;
(4)若顶点w尚未被访问则深度优先搜索递归访问顶点w;
(5)查找顶点v的w邻接顶点的下一个邻接顶点w,转到步骤(3)。 - 通俗理解
图的深度遍历算法类似于树的先根遍历,但是需要根据邻接顶点的存储次序访问每个邻接顶点。 - 无向连通图
如图12,式(1)(2)为其顶点集合和邻接矩阵。我们从A顶点开始深度遍历,遍历的次序应为:ABCDE。其中遍历到D顶点时,发现其没有未访问的邻接顶点,则会回溯到B,访问B的邻接顶点E。这与树的先根遍历是类似的,树的先根遍历是先访问根节点,再按照从左到右的次序访问每棵子树;图的话是先访问一个顶点,再按照其邻接顶点的存储次序访问每个邻接顶点。
V = [ A B C D E ] (1) V= \begin{bmatrix} A \\ B \\ C \\ D \\ E \\ \end{bmatrix} \tag{1} V=⎣⎢⎢⎢⎢⎡ABCDE⎦⎥⎥⎥⎥⎤(1)
E = [ 0 1 0 0 1 1 0 1 0 1 0 1 0 1 0 0 0 1 0 1 1 1 0 1 0 ] (2) E= \begin{bmatrix} 0 & 1 & 0 & 0 & 1\\ 1 & 0 & 1 & 0 & 1\\ 0 & 1 & 0 & 1 & 0\\ 0 & 0 & 1 & 0 & 1\\ 1 & 1 & 0 & 1 & 0 \end{bmatrix} \tag{2} E=⎣⎢⎢⎢⎢⎡0100110101010100010111010⎦⎥⎥⎥⎥⎤(2)
- 有向连通图
有向连通图与无向连通图不同的是:
1, 有向连通图边是有方向的,如图13,B邻接到顶点是C,但C是没有邻接到任何顶点。
2, 无向连通图、有向强连通图从任一顶点出发都可以遍历全图,但有向连通图不一定,如图13为有向连通图但非强连通图,从顶点C是不能遍历全图的。
其中式(3)(4)为图13的顶点集合和邻接矩阵。我们从A顶点开始深度遍历,遍历的次序应为:ABCED。其中遍历到D顶点时,发现其没有邻接到任何顶点,则会回溯到B,发现B也没有邻接到任何顶点,再回溯到A,发现A邻接到顶点E,则访问E,由于E邻接到的B已访问,下一个则会访问顶点D。
V
=
[
A
B
C
D
E
]
(3)
V= \begin{bmatrix} A \\ B \\ C \\ D \\ E \\ \end{bmatrix} \tag{3}
V=⎣⎢⎢⎢⎢⎡ABCDE⎦⎥⎥⎥⎥⎤(3)
E
=
[
0
1
0
0
1
0
0
1
0
0
0
0
0
0
0
0
0
1
0
0
0
1
0
1
0
]
(4)
E= \begin{bmatrix} 0 & 1 & 0 & 0 & 1\\ 0 & 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 0 & 0\\ 0 & 0 & 1 & 0 & 0\\ 0 & 1 & 0 & 1 & 0 \end{bmatrix} \tag{4}
E=⎣⎢⎢⎢⎢⎡0000010001010100000110000⎦⎥⎥⎥⎥⎤(4)
广度遍历(连通图)
- 递归算法思想
若图为空则算法结束;否则:
(1)访问初始顶点v并标记顶点v为已访问;
(2)顶点v入队列;
(3)当队列非空时则继续执行,否则算法结束;
(4)出队列取得队头顶点u;
(5)查找顶点u的第一个邻接顶点w;
(6)若顶点u的邻接顶点w不存在,则转到步骤(3),否则循环执行:
(6.1)若顶点w尚未被访问则访问顶点w并标记顶点w为已访问;
(6.2)顶点w入队列;
(6.3)查找顶点u的w邻接顶点后的下一个邻接顶点w,转到步骤(6)。 - 通俗理解
图的广度遍历算法类似于树的层序遍历,一层一层的遍历,每一层再按照顶点的存储次序访问每个邻接顶点。 - 无向连通图
如图14,式(5)(6)为其顶点集合和邻接矩阵。我们从A顶点开始深度遍历,可以把A、BE、C、D依次作为第一、二、三、四层,遍历的次序应为:ABECD。
V = [ A B C D E ] (5) V= \begin{bmatrix} A \\ B \\ C \\ D \\ E \\ \end{bmatrix} \tag{5} V=⎣⎢⎢⎢⎢⎡ABCDE⎦⎥⎥⎥⎥⎤(5)
E = [ 0 1 0 0 1 1 0 1 0 1 0 1 0 1 0 0 0 1 0 1 1 1 0 1 0 ] (6) E= \begin{bmatrix} 0 & 1 & 0 & 0 & 1\\ 1 & 0 & 1 & 0 & 1\\ 0 & 1 & 0 & 1 & 0\\ 0 & 0 & 1 & 0 & 1\\ 1 & 1 & 0 & 1 & 0 \end{bmatrix} \tag{6} E=⎣⎢⎢⎢⎢⎡0100110101010100010111010⎦⎥⎥⎥⎥⎤(6)
- 有向连通图
如图15,式(7)(8)为其顶点集合和邻接矩阵。我们从A顶点开始深度遍历,可以把A、BE、CD依次作为第一、二、三层,遍历的次序应为:ABECD。
V
=
[
A
B
C
D
E
]
(7)
V= \begin{bmatrix} A \\ B \\ C \\ D \\ E \\ \end{bmatrix} \tag{7}
V=⎣⎢⎢⎢⎢⎡ABCDE⎦⎥⎥⎥⎥⎤(7)
E
=
[
0
1
0
0
1
0
0
1
0
0
0
0
0
0
0
0
0
1
0
0
0
1
0
1
0
]
(8)
E= \begin{bmatrix} 0 & 1 & 0 & 0 & 1\\ 0 & 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 0 & 0\\ 0 & 0 & 1 & 0 & 0\\ 0 & 1 & 0 & 1 & 0 \end{bmatrix} \tag{8}
E=⎣⎢⎢⎢⎢⎡0000010001010100000110000⎦⎥⎥⎥⎥⎤(8)
非连通图的遍历
对于非连通图,从图的任意一个顶点开始深度或广度优先遍历并不能访问图中的所有顶点。只能访问和初始顶点连通的所有顶点。但是,每一个顶点都作为一次初始顶点进行深度优先遍历或广度优先遍历,并根据顶点的访问标记来判断是否需要访问该顶点,就一定可以访问非连通图中的所有顶点,其中每个连通分量的遍历方法与前面类似,这里的话就不举例了。
读到结尾啦,小僧总结一下吧:树其实是图的一种特殊情况,二叉树其实是树的特殊情况,从图到树再到二叉树,边的数目减少,结点的存储结构也更加简单(从无序变得有序),相应的我们的遍历方式也更加明确。到这里小僧也累啦,留几道思考题给大家吧👇,答案在评论区
小僧初来乍到,希望大家多多支持!如有纰漏,欢迎大家在评论区留言,小僧会及时更正,同时,大家如果有疑惑的地方可以把问题写在评论区,小僧看到会回复哟😄!
另外,小僧的公众号上线啦,里面有“实用工具”、“学习资源”以及许多资料,欢迎大家关注一波小僧的公众号👇
参考:
《数据结构——使用C语言(第五版)》 ------朱战立