BFS&DFS

196 阅读7分钟

  新的方法和概念,常常比解决问题本身更重要。——华罗庚


目录

1 引子

2 深度优先搜索(DFS)

过程

图解

模板

代码

适用范围

3 广度优先搜索(BFS)

过程

图解

模板

代码

适用范围

4 DFS与BFS的区别


1 引子

图是一种灵活的数据结构,一般作为一种模型用来定义对象之间的关系或联系。对象由顶点(V)表示,而对象之间的关系或者关联则通过图的边(E)来表示。
图可以分为有向图和无向图,一般用G=(V,E)来表示图。经常用邻接矩阵或者邻接表来描述一副图。
在图的基本算法中,最初需要接触的就是图的遍历算法,根据访问节点的顺序,可分为广度优先搜索(BFS)和深度优先搜索(DFS)。

2 深度优先搜索(DFS)

深度优先搜索(Depth First Search,DFS):一种用于遍历或搜索树或图的算法。沿着树的深度遍历图的节点,尽可能深的搜索树的分支。当节点v的所在边都已都已被搜索过或者在搜索时节点不满足条件,搜索将回溯到发现节点v的那条边的起始节点。整个进程反复进行直到所有节点都被访问为止。属于盲目搜索,最糟糕的情况下算法的时间复杂度为O(n!)。

做一个形象的比喻:dfs好像是在走迷宫,得一直走到头,看看路的尽头是不是出口,如果是,就直接走出去;如果不是,那就返回上一个“标记点”寻找不一样的可行方法。dfs的实现关键在于回溯

过程

(1)从图中某个顶点v出发,访问v。

(2)找出刚访问过的顶点的第一个未被访问的邻接点,访问该顶点。以该顶点为新顶点,重复此步骤,直至刚访问过的顶点没有未被访问的邻接点为止。

(3)返回前一个访问过的且仍有未被访问的邻接点的顶点,找出该顶点的下一个未被访问的邻接点,访问该顶点。

(4)重复步骤(2)和(3),直至图中所有顶点都被访问过,搜索结束。


图解

假设从0开始访问所有节点:

​编辑

 首先访问6,然后访问8,由于8的邻接点都已被访问,此步结束。

 ​编辑

 然后回到6,再回到0,访问5,访问2。

 ​编辑

 回到5,访问4。

 ​编辑

 依次返回5,0,开始访问1,3,7,走完所有节点。

 ​编辑

DFS路径:0->6->8->5->2->4->1->3->7


模板

int dfs(int u)
{
st[u] = true; // st[u] 表示点u已经被遍历过

    for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j]) dfs(j);
}
}

代码

#include<iostream>
using namespace std;
int visit[101],count,n,g[101][101];
void dfs(int cur){
	int i;
	cout<<cur<<" ";
	for(int i=1;i<=n;i++){
		if(g[cur][i]==1 && visit[i]==0){
			visit[i]=1;
			dfs(i);
		}
	}
}
int main(){
	int i,j,m,a,b;
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>a>>b;
		g[a][b]=1;
		g[b][a]=1;
	}
	visit[1]=1;
	dfs(1);
	return 0;
}

适用范围

1.排列组合问题

2.N*N走迷宫问题

3.连通块

3 广度优先搜索(BFS)

广度优先搜索(Breadth First Search,BFS):属于一种盲目搜寻法,目的是系统地展开并检查图中所有的节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。因为所有的节点必须被储存,因此BFS的空间复杂度为O(\left | V \right |+\left | E \right |),其中\left | V \right |是节点的数目,而\left | E \right |是图中边的数目。

再来一个形象的比喻:你是一个高度近视者,有一次起床,你的眼镜找不到了,于是你就在四周搜索,直到慢慢找出你的眼镜。BFS一般用队列实现,因为队列先进先出的模式很契合BFS的判定方式——即先遍历所有可行的下一步,再放入队中,再从队顶一个一个判定结果,循环执行直到得到结果。

过程

(1)从图中某个顶点v出发,访问v。

(2)依次访问v的各个未曾访问过的邻接点。

(3)分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问。重复步骤(3),直至图中所有已被访问的顶点的邻接点都被访问到。


图解

以之前的图为例,还是从0出发。

​编辑

首先访问节点0的邻接点1,5,6,8.

​编辑

 然后按次序,先访问1的邻接点3,再访问5的邻接点2和4,由于节点6和8没有未被访问过的邻接点,此步结束。至此所有节点都已被访问过。

​编辑

BFS路径:0->1->5->6->8->3->7->2->4


模板

queue q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);

while (q.size())
{
int t = q.front();
q.pop();

    for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j])
{
st[j] = true; // 表示点j已经被遍历过
q.push(j);
}
}
}

代码

#include<iostream>
#include<queue>
using namespace std;
int visit[101],cnt,n,g[101][101];
int main(){
  int i,j,m,a,b;
  cin>>n>>m;
  for(int i=1;i<=m;i++){
  	cin>>a>>b;
  	g[a][b]=1;
  	g[b][a]=1;
  }
  visit[1]=1;
  queue<int> q;
  cnt=1;
  q.push(1);
  cout<<1<<" ";
  while(!q.empty() && cnt<n){
  	int cur=q.front();
  	for(int i=1;i<=n;i++){
  		if(g[cur][i]==1 && visit[i]==0){
  			cout<<i<<" ";
  			q.push(i);
  			cnt++;
  			visit[i]=1;
  		}
  		if(cnt>=n)
  			break;
  	}
  	q.pop();
  }
  return 0;
}

适用范围

1.最短距离问题

2.大范围查找

3.层序遍历


4 DFS与BFS的区别

深度优先搜索(DFS)广度优先搜索(BFS)
1.DFS从根节点开始搜索,并从根节点尽可能远地探索这些节点。2.使用堆栈数据结构来记住下一个节点访问。3.DFS所需的内存少于BFS所需的内存。4.它是通过LIFO列表应用的。5.寻找最短距离的理想选择。6.该算法用于解决问题,拓扑排序,需要对图进行回溯,识别图中的循环以及发现两个节点之间的路径等。1.BFS从根节点开始搜索,并根据树级别模式探索所有邻居根。2.它使用队列数据结构来记住下一个节点访问。3.BFS比DFS需要更多的内存。4.它是使用FIFO列表应用的。5.寻找最短路径的理想选择。6.该算法用于查找两个节点之间的最短路径,发现图中所有的连接组件,分析图是否为二部图等。

如有错误和不足,敬请指正。