react 递归遍历四层树结构 遍历分支中的最后一个节点_图的两种遍历

64a7a61bc44ec3902944c40b5084db18.png

邻接矩阵和邻接表

在讲数据结构的那一节里我们提到了图这种数据结构,并且介绍了两种用来表示图的方法:邻接矩阵和邻接表。接下来我们来分别看看它们是如何表示一张图的吧。

对于图而言,节点之间的连接是不受限制的,即一个节点可以连接任意节点(包括自身),并且还具有方向性。所以,我们可以用 0 和 1 两种状态来表示一个节点

是否能够与另一个节点
直接相连。假设一个图由
个节点组成,那么我们就有
种可能的连接。为了更好地表示这些数据,我们用一个
的矩阵:

9918e3d5d42b36d8c6b4e3fa3f9fe80f.png

根据上面的矩阵,我们可以得知节点

与节点
连接,而节点
却没有直接与节点
相连。如果我们将矩阵中的每一项都考虑在内,构建节点与节点的连接关系,并直观地展现出来就会和下面一样:

583771736181563c5cb8f7859c142e57.png

如果我们用一个链表的形式来表示一个节点到与之相连的全部节点,那么我们就可以得到一个由多个链表构成的数组。举个例子,上面的图中,节点

对应的链表中含有节点
,说明
节点
是直接连接到节点
和节点
。同样,由于我们的图是一个无向图,所以 节点
和节点
分别对应的链表也是含有节点
。根据整个邻接表,我们也可以画出和上面一样的图。

深度优先遍历

在了解两种遍历算法之前,我们先要知道什么是遍历。遍历traversal是对树或图这种相对复杂的数据结构中的每一个节点进行逐一访问的过程

先来介绍一下深度优先遍历depth-first search),顾名思义它与深度有关系。其主要思想是:从一个节点出发,我们需要尽可能远地探索未访问的节点,直到不能继续探索为止,也就是遇到了“死胡同”(dead end),举个例子,我们要对下面的图进行遍历。

47f96e0d7d4ba6e9471537277a9d797f.png

假设从节点

出发,我们
首先将节点 a 标记为已访问

a487972376909fa4185759b4a1fdd01a.png

访问完节点

之后,我们要
尽可能地远的探索其他节点,直到遇到“死胡同”。因此,在遇到“死胡同”之前,我们会访问节点
,
,
,
(如出现分支则按照字母表顺序进行优先选择)。

7029dadbc44d9e25e5deb373f07f2eae.png

当节点

被访问后,我们需要先退回到节点
,然后再继续探索,这时我们发现节点
已经没有可探索的节点了,因此我们要再退到节点
,然后再尽可能地探索其他节点,直到再次遇到“死胡同”。因此节点
是下一个被访问的节点。

a99eeb6d2576841b6f9b25d13bdf67c8.png

然后重复上述的过程,直到我们退回到了节点

0d551abcb40ba617710b2eb62f91db54.png

从上面的过程我们可以看出,深度优先遍历是需要用递归或者栈来实现的(因为后加入的节点要先离开),要实现这个代码,首先我们要用一个类来表示图中的节点。

class 

然后实现深度优先遍历的过程:

def 

我们看到,由于节点

和节点
没有与左边的图连通,所以在遍历中没有被访问到,因此需要再构建一个函数用来
遍历所有节点,来看这些节点是否被访问过,如果没有,我们就从该节点开始继续遍历
def 

因此,我们要从节点

出发,再遍历剩下的节点,直到所有的节点都被遍历。

a0decad9355436a4df0f7250eb18b0f1.png

广度优先遍历

和深度优先遍历不同的是,广度优先遍历breadth-first search尽可能广(多)地遍历其他相邻的节点,也就是说节点是一层一层遍历的

还是那上面的图为例,要实现广度优先遍历,需要用到队列这种数据结构。

1ba308357303134905d03dbf488e32e9.png

首先是节点

入队,并标记
已被访问。

09d26031e52837e7e3724f833dc9868f.png

接下来

的相邻节点
入队,
出队,同时标记节点
已被访问。

8246454c9642397be282f8369ec401bd.png

此时节点

的所有相邻节点全部被访问了,所以此时只需要考虑节点
和节点
的相邻节点,因此下一次被访问的节点就是

b8d8357f20bbda883a84b82a14a2f5c5.png

然后我们重复这个过程,最后得到:

448f6af183be0066e841d4e45efeb288.png

下面是代码实现过程:

def 

同样,对于那些没有相互连接的图,我们处理的方式跟上面是一样的。

def 

因此我们要再从节点

开始,遍历剩下的图。

afcdbc19c81b78d610ed9f4ecd0c144f.png

1c660195e71416cff2ea7baaf9d715a0.png

那么图的遍历跟暴力求解有什么关系呢?事实上,不管是深度优先遍历还是广度优先遍历,我们可以发现,每一个节点都执行了一次遍历算法,正是通过这种方式,才保证了图的每一个节点能够被访问,达到遍历的效果。后面我们讲到的暴力搜索也是同样的思想。

复杂度分析

下面来谈谈遍历图的复杂度。我们知道图可以由邻接矩阵或邻接表来表示,因此我们要分别来讨论:

如果我们用邻接矩阵来表示,在执行遍历算法的过程中,由于每个节点都要查看其他节点是否与自己相连,即矩阵中每一行的 0 和 1,所以对于一个有

个节点的图来讲,就要查看
,其中
表示判断节点是否已经被访问过的次数。因而用邻接矩阵来表示的图的两种遍历的复杂度均为

邻接表由于只记录了每个节点和与之相连节点的信息,即每条边的信息。所以在遍历的过程中,我们需要

,其中
表示一个图里边的条数(具有两个方向的边算作两次)。因此用邻接表表示的图的两种遍历的复杂度均为

→本节全部代码←

← 字符串匹配(BF)| 算法与复杂度​zhuanlan.zhihu.com
74b8dcfeb85d1c2c74a0d6bc7ef19e35.png
→ 最近点对与凸包问题(BF)| 算法与复杂度​zhuanlan.zhihu.com
74b8dcfeb85d1c2c74a0d6bc7ef19e35.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值