欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【数据结构】图的遍历——深度优先搜索DFS、广度优先搜索BFS

程序员文章站 2022-07-13 09:39:37
...

深度优先搜索(Depth First Search)DFS
给定一个图,里面有8个灯泡,给定一个起点,要求把里面的灯泡全部点亮,请问应该如何操作
【数据结构】图的遍历——深度优先搜索DFS、广度优先搜索BFS
先给其标注1~8,然后1是起点(已点亮),接下来的步骤是:
从1出发,1的连通点为3个:2、5、7,1可以向其中任意一点前进并开灯,假如向2走
到了2点,2的连通点为:1、3,由于1已经点亮了并且3没点亮,因此可以向3走
到了3点,3的连通点情况与2类似,向4走
到了4点,4的连通点为3个:5、6、7,可以向其中任意一点前进,假如往7走
到了7点,连通点为3个:1、4、8,1相当于回到起点,4是亮着的,但由于还有灯(8)没点亮,因此向8走
到了8点,点亮情况如图:
【数据结构】图的遍历——深度优先搜索DFS、广度优先搜索BFS
此时8的连通点为7,7是亮着的,已经无路可走了,这能说明灯全部亮了吗?显然不能,这时候的方法为:8退回之前点亮它的点再看是否有其它点可以被点亮
因此回到7,7的连通点全都点亮(无路可走),于是再退回到4
此时4发现还有2个点:5、6两个点没有被点亮,因此可以任意选择一点,比方说走到6
到了6点,同样地,走到了5点
此时在5点的连通点为:1、4、6,这三个点都已经点亮了,1是最初的起点,但这依然不能说明灯全部点亮,于是5继续退回到6,6退回到4,以此类推,直到退回到1,并且1也是无路可走的情况,即表示灯已经被全部点亮了
这就是BFS的基本思路。
再提到“原路返回”,可以理解为递归,当然也是一个堆栈的“入栈出栈”的过程
递归伪代码描述:

void DFS(Vertex V){
	visited[V] = true;//是否访问
	for(V的每个邻接点W)
		if(!visited[W])
			DFS(W);
}

类似树的先序遍历
若有N个顶点、E条边,时间复杂度是:
·用邻接表存储图,有O(N+E)
·用邻接矩阵存储图,有O(N²)

广度优先搜索(Breadth First Search)BFS
在树里面,广度优先搜索就相当于是层序遍历
先回顾一下树的层序遍历:
【数据结构】图的遍历——深度优先搜索DFS、广度优先搜索BFS
我们是使用队列,top出队,top的左右子树入队(先左再右),再top出队、top的左右子树入队,直到队列为空为止
层序遍历的顺序就是top出队的顺序,在树种就是从根节点开始自左向右、自上而下的顺序输出
对于图来讲,树是一种特殊的图,所以图也有可能通过层序遍历的方式实现:
【数据结构】图的遍历——深度优先搜索DFS、广度优先搜索BFS
先规定起点1,将其压入队列
然后出队,将1周围与其相关的点全部压入队列
【数据结构】图的遍历——深度优先搜索DFS、广度优先搜索BFS
然后下一轮类似,以此类推
【数据结构】图的遍历——深度优先搜索DFS、广度优先搜索BFS
这样出来的顺序就是图从中心点开始的每一圈的顺序,这就是图的“层次遍历”,也就是BFS
代码和层次遍历几乎相同:

void BFS(Vertex V){
	visited[V] = true;
	Enqueue(V,Q);//头结点入队,Q是队列,出队入队都不是模板类,当然用模板类也是一样的
	while(!IsEmpty(Q)){
		V = Dequeue(Q);//出队
		for(V的每个邻接点W)
			if(!visited[W]){
				visited[W] = true;
				Enqueue(W,Q);//新点,入队
			}
	}
}

若有N个顶点、E条边,时间复杂度是:
·用邻接表存储图,有O(N+E)
·用邻接矩阵存储图,有O(N²)
与DFS一样

·为什么需要两种遍历?
这两种遍历各有不一样的特点,用下图举例:
【数据结构】图的遍历——深度优先搜索DFS、广度优先搜索BFS
左上角为1,绿色为终点,如果是DFS的话,给定一个方向(先上,而后顺时针方向),这样它会先向右走,然后到一个可以走的点,继续按照规定的方向走,直到走进一个死胡同,然后再退回到之前的格子,继续下一步,按照视频中的例子,DFS的路径会是:
【数据结构】图的遍历——深度优先搜索DFS、广度优先搜索BFS
如果是BFS的话,BFS的搜索可以理解为一圈一圈进行入队、搜索,如果目标点所在深度不深的话,BFS就能较快找到终点
【数据结构】图的遍历——深度优先搜索DFS、广度优先搜索BFS
当然这只是一种特殊情况,DFS效率甚至还和走的方向有关,当然这个说法过于“玄学”,因此不考虑
但如果是另一种情况:终点在图的右下角,那么用BFS的话便会非常辛苦,效率很低,这时候DFS的优势就出来了。
结论:BFS适合节点度比较大的情况(方向多),DFS适合节点度小的情况(方向少)

·图不连通怎么办?
在图中如果存在孤立的节点应该怎么办呢?
前提知识:名词解释
连通:如果从V到W存在一条(无向)路径,则称V和W是连通的
路径:V到W的路径是一系列顶点的集合,其中任一对相邻的顶点间都有图中的边。路径的长度是路径中的边数(如果带权,则是所有边的权重和)。如果V到W之间的所有顶点都不同,则称其为简单路径
回路:起点等于终点的路径
连通图:图中任意两点均连通
不连通的图→连通分量:无向图的极大连通子图
·极大顶点数:再加一个顶点就不连通了
·极大边数:包含子图中所有顶点相连的所有边
【数据结构】图的遍历——深度优先搜索DFS、广度优先搜索BFS
对于第一个图,本身是连通的,再加一个E就不连通的,因此这个是其连通分量。其余类似
对于有向图:
【数据结构】图的遍历——深度优先搜索DFS、广度优先搜索BFS
解决方法:
【数据结构】图的遍历——深度优先搜索DFS、广度优先搜索BFS