二十三、图的遍历(BFS和DFS)
一、概念
图的遍历(Traversing Graph)从某一顶点出发,访问图中所有顶点,且使每一顶点仅被访问一次。与树的遍历不同的是,图的遍历需要处理两种特殊情况:一是从某一顶点出发进行遍历时,可能访问不到所有其他顶点,比如非连通图;二是有些图存在回路,必须保证遍历过程不能因为回路陷入死循环。
图的遍历是解决图的许多应用问题的基础,如路径问题、连通性问题等。图的遍历有两种基本方法:
- 深度优先遍历(Depth-First Search, DFS)
- 广度优先遍历(Breadth-First Search, BFS)
二、深度优先遍历
深度优先遍历,类似于树的前序遍历的过程,以“深度”作为第一关键词,沿着一条路径直到无法继续前进,才退回到路径上离当前顶点最近的还存在未访问分支顶点的岔道口,并前往访问那些未访问分支顶点,直到遍历完整个图。
过程如下:
- 从图中某个顶点 $v$ 出发,访问 $v$ 。
- 找出刚访问过的顶点的第一个未被访问的邻接点,访问该顶点。以该顶点为新顶点,重复此步骤,直至刚访问过的顶点,没有未被访问的邻接点为止。
- 返回前一个访问过的且仍有未被访问的邻接点的顶点,找出该顶点的下一个未被访问的邻接点,访问该顶点。
- 重复步骤2和3,直至图中所有顶点都被访问过,搜索结束。
DFS的具体实现
DFS遍历图的基本思路就是将经过的顶点设置为已访问,在下次递归碰到这个顶点的时就不再去处理,直到整个图的顶点都被标记为已访问。
动图演示

邻接矩阵
1.连通图的深度优先遍历
Status DFS_M(MGraph G, int k, Status(*visit)(int))
{ //从连通图G的k顶点出发进行深度优先遍历,图G采用邻接数组存储结构
int i;
if (visit(k) == ERROR) //访问 k 顶点
return ERROR;
G.tags[k] = VISITED;
for (i = FirstAdjVex_M(G, k); i >= 0; i = NextAdjVex_M(G, k, i))
{
if (G.tags[i] == UNVISITED) //位序为i的邻接顶点未被访问过
if (DFS_M(G, i, visit) == ERROR) //对 i 顶点递归深度遍历
return ERROR;
}
return OK;
}
2.图的深度优先遍历
Status DFSTraverse_M(MGraph G, Status(*visit)(int))
{ //深度优先遍历采用邻接数组存储结构的图G
int i;
for (i = 0; i < G.n; i++)
G.tags[i] = UNVISITED; //初始化标志数组
for (i = 0; i < G.n; i++)
if (G.tags[i] == UNVISITED) //若i顶点未访问,则以其为起点进行深度优先遍历
if (DFS_M(G, i, visit) == ERROR)
return ERROR;
return OK;
}
深度优先搜索遍历的算法分析
在遍历图时,对图中每个顶点至多调用一次DFS函数,因为一旦某个顶点被标志成已被访问,就不再从它出发进行搜索。因此,遍历图的过程实质上是对每个顶点查找其邻接点的过程,其耗费的时间则取决于所采用的存储结构。
- 当用邻接矩阵表示图时,查找每个顶点的邻接点的时间复杂度为 $O({n^2})$,其中 $n$ 为图中顶点数。
- 当以邻接表做图的存储结构时,查找邻接点的时间复杂度为 $O(e)$,其中 $e$ 为图中边数。由此,当以邻接表做存储结构时,深度优先搜索遍历图的时间复杂度为 $O(n+e)$。
三、广度优先遍历
广度优先遍历,类似于树的按层次遍历的过程,以“广度”作为关键词,每次以扩散的方式向外访问顶点。
过程如下:
- 从图中某个顶点 $v$ 出发,访问 $v$ 。
- 依次访问 $v$ 的各个未曾访问过的邻接点。
- 分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问。重复步骤3,直至图中所有已被访问的顶点的邻接点都被访问到。
BFS的具体实现
使用BFS遍历图的基本思想是建立一个队列,并把初始顶点加入队列,此后每次都取出队首顶点进行访问,并把从该顶点出发可以到达的未曾加入过队列(而不是未访问)的顶点全部加入队列,直到队列为空。
动图演示

邻接表
1.图的广度优先遍历
Status BFSTraverse_AL(ALGraph G, Status(*visit) (int))
{ //广度优先遍历采用邻接表存储结构的图G
int i, j, k;
AdjVexNodeP p;
LQueue Q;
InitQueue_LQ(Q); //初始化链队列Q
for (i = 0; i < G.n; i++)
G.tags[i] = UNVISITED; //初始化标志数组
for (i = 0; i < G.n; i++) //依次检查所有顶点
if (G.tags[i] == UNVISITED) // i 顶点未被访问
{
if (visit(i) == ERROR)
return ERROR;
G.tags[i] = VISITED;
EnQueue_LQ(Q, i); //访问 i 顶点,并入队
while (DeQueue_LQ(Q, k) == OK) //出队元素到 k
{
for (j = FirstAdjVex_AL(G, k, p); j >= 0; j = NextAdjVex_AL(G, k, p))
//依次判断 k 顶点的所有邻接顶点j, 若未曾访问,则访问它,并入队
{
if (G.tags[j] == UNVISITED)
{
if (visit(j) == ERROR)
return ERROR;
G.tags[j] = VISITED;
EnQueue_LQ(Q, j);
}
}
}
}
return OK;
}
广度优先搜索遍历的算法分析
由算法可知,每个顶点至多进一次队列。遍历图的过程实质上是通过边找邻接点的过程, 因此广度优先搜索遍历图的时间复杂度和深度优先搜索遍历相同。两种遍历方法的不同之处仅仅在于对顶点访问的顺序不同。
- 当用邻接矩阵表示图时,查找每个顶点的邻接点的时间复杂度为 $O({n^2})$,其中 $n$ 为图中顶点数。
- 当以邻接表做图的存储结构时,广度优先搜索遍历图的时间复杂度为 $O(n+e)$。
二十三、图的遍历(BFS和DFS)的更多相关文章
- 图的遍历BFS广度优先搜索
图的遍历BFS广度优先搜索 1. 简介 BFS(Breadth First Search,广度优先搜索,又名宽度优先搜索),与深度优先算法在一个结点"死磕到底"的思维不同,广度优先 ...
- 图的遍历BFS
图的遍历BFS 广度优先遍历 深度优先遍历 可以进行标记 树的广度优先遍历,我们用了辅助的队列 bool visited[MAX_VERTEX_NUM] //访问标记数组 //广度优先遍历 void ...
- 数据结构(三十二)图的遍历(DFS、BFS)
图的遍历和树的遍历类似.图的遍历是指从图中的某个顶点出发,对图中的所有顶点访问且仅访问一次的过程.通常有两种遍历次序方案:深度优先遍历和广度优先遍历. 一.深度优先遍历 深度优先遍历(Depth_Fi ...
- 图的遍历算法:DFS、BFS
在图的基本算法中,最初需要接触的就是图的遍历算法,根据访问节点的顺序,可分为深度优先搜索(DFS)和广度优先搜索(BFS). DFS(深度优先搜索)算法 Depth-First-Search 深度优先 ...
- Kruskal和prime算法的类实现,图的遍历BFS算法。
一.图的遍历 #include<iostream> #include<queue> #include<vector> using namespace std; in ...
- 图的遍历——BFS
原创 裸一篇图的BFS遍历,直接来图: 简单介绍一下BFS遍历的过程: 以上图为例子,从0开始遍历,访问0,按大小顺序访问与0相邻的所有顶点,即先访问1,再访问2: 至此顶点0已经没有作用了,因为其本 ...
- Java实现 洛谷 P3916 图的遍历(反向DFS+记忆化搜索)
P3916 图的遍历 输入输出样例 输入 4 3 1 2 2 4 4 3 输出 4 4 3 4 import java.io.BufferedReader; import java.io.IOExce ...
- 模板 图的遍历 bfs+dfs 图的最短路径 Floyed+Dijkstra
广搜 bfs //bfs #include<iostream> #include<cstdio> using namespace std; ],top=,end=; ][]; ...
- 《算法设计手册》面试题解答 第五章:图的遍历 附:DFS应用之找挂接点
第五章面试题解答 5-31. DFS和BFS使用了哪些数据结构? 解析: 其实刚读完这一章,我一开始想到的是用邻接表来表示图,但其实用邻接矩阵也能实现啊?后来才发现应该回答,BFS用队列实现:DFS可 ...
- 图的遍历——BFS(队列实现)
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> ...
随机推荐
- 【Kubernetes】K8s笔记(十一):Ingress 集群进出流量总管
目录 0. Ingress 解决了什么问题 1. Ingress Controller 2. 指定 Ingress Class 使用多个 Ingress Controller 3. 使用 YAML 描 ...
- (数据科学学习手札145)在Python中利用yarl轻松操作url
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,在诸如网络爬虫.web应用开发 ...
- wpf下的图片放大缩小
WPF下实现图片的放大缩小移动 在windows 7里面有自带的图片查看器,这个软件可以打开一张图片然后以鼠标在图片中的焦点为原点来进行缩放,并且放大后可以随意拖动.下面我们在WPF中实现这个功能 ...
- .NET中的拦截器filter的使用
拦截器的使用 使用场景分析 我们先想像一个场景,就是程序员开发软件,他是怎么工作的呢?我们都知道,普通的程序员只需要根据需求文档开发相应的功能即可,他不用和客户谈论软件需求,不用理会软件卖多少钱,他要 ...
- 虚拟机里网络连接的几种方式说明(桥接,NAT, 仅主机)
虚拟机里网络连接类型的选择: 桥接:选择桥接模式的话虚拟机和宿主机在网络上就是平级的关系,相当于连接在同一交换机上. NAT:NAT模式就是虚拟机要联网得先通过宿主机才能和外面进行通信. 仅主机:虚拟 ...
- 基于案例分析 MySQL Group Replication 的故障检测流程
故障检测(Failure Detection)是 Group Replication 的一个核心功能模块,通过它可以及时识别集群中的故障节点,并将故障节点从集群中剔除掉.如果不将故障节点及时剔除的话, ...
- ubuntu20.04修改静态ip不生效问题
一.前言 最近从头开始配置hadoop的时候,由于想切换到NAT模式下配置hadoop,但在修改ip的时候发现设置了静态ip,但ip不生效,查了很多资料,发现由于配置信息写错了. 二.解决问题 ifc ...
- 【Java并发005】原理层面:volatile关键字全解析
一.前言 在Java 5之前,volatile是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然 ...
- 网络编程:多进程实现TCP服务端并发、互斥锁代码实操、线程理论、创建线程的两种方式、线程的诸多特性、GIL全局解释器锁、验证GIL的存在
目录 多进程实现TCP服务端并发 互斥锁代码实操 线程理论 创建线程的两种方式 线程的诸多特性 GIL全局解释器锁 验证GIL的存在 GIL与普通互斥锁 python多线程是否有用 死锁现象 多进程实 ...
- java集合类 collection接口,List集合
java集合类:collection接口,List集合 在java.util包中提供了一些集合类,集合类又被称为容器,常用的有List集合,Set集合,Map集合.下面将介绍collection接口和 ...