一、深度优先搜索的定义
在前面我们学习递归的时候提到递归树,用递归的手段可以遍历到这个树形结构。其实对于任意一张有联通关系的图,我们同样可以使用递归的手段进行遍历。
其中以深度为第一优先级进行遍历的方式我们称为:深度优先搜索(dfs),即沿着一条路一直走,直到走不通。
像这样一张图,存在一个环。
通常情况下,如果对于某点我们只需要遍历一次的话,那我们要对走过的点进行标记,否则会形成死循环。(一直在环内部搜索)
所以在dfs时候,需要进行标记。即未遍历过的点标记为0,遍历过的点标记为1,标记为1的点则不再进行遍历。
本图的正常枚举顺序为:1 - 2 - 5 - 9 - 10 - 6 - 3 - 7- 4 - 8
二、dfs的连通性处理
判断点x和点y是否连通,即从点x开始进行搜索遍历,如果按照规则能正常遍历到y,则说明连通。
三、dfs的连通计数问题
使用dfs可以访问从起点出发的且符合条件的所有点,因此一次dfs能够把所有符合条件的点全部标记出来,此时如果在dfs中加一个计数,就可以很轻松得到本次dfs访问过的点的数量。简单来说就是,把dfs当做一个标记地盘的工具。连通块大小,就是我们占的地盘大小。
四、dfs的连通块计数
我们刚才提到一次dfs可以枚举到从当前起点出发的符合条件的所有点,即可以将所有与之直接或间接连通的点找到。我们把这样的点集可以称作一个连通块。
当然一个图上连通块的数目不一定唯一。如下图所示,一共有5个连通块。
如何进行连通块数目统计?
一次dfs可以标记一个连通块,因此我们在一张图中找到符合条件的起点开始dfs,连通块的数量则等价于dfs执行的次数。
以图为例:
逐行逐列进行枚举,当找到值为0的点,进行一次dfs,并把枚举到的所有点值修改为1,这样我们甚至不用标记是否访问。
显然,一共需要进行5次dfs
五、题目练习+讲解
1.题目
有一个仅由数字 0 与 1 组成的 n×n 格迷宫。若你位于一格 0 上,那么你可以移动到相邻 4 格中的某一格 1 上,同样若你位于一格 1 上,那么你可以移动到相邻 4 格中的某一格 0 上。你的任务是:给定m个询问,求出从某一格开始能移动到多少个格子(包含自身)。(n<=1000,m<=10000)
2.题解
#include<bits/stdc++.h>
using namespace std;
int n,m;
char a[1005][1005];
int vis[1005][1005];
int dx[5]={0,-1,0,1,0};
int dy[5]={0,0,1,0,-1};
int id,cnt;
int p[1005][1005];
int size[1000005];
void dfs(int x,int y){
p[x][y]=id;
cnt++;
for(int k=1;k<=4;k++){
int i=x+dx[k];
int j=y+dy[k];
if(i<1||i>n||j<1||j>n){
continue;
}
if(a[i][j]==a[x][y]){
continue;
}
if(vis[i][j]){
continue;
}
vis[i][j]=1;
dfs(i,j);
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(vis[i][j]==0){
vis[i][j]=1;
cnt=0;
id++;
dfs(i,j);
size[id]=cnt;
}
}
}
while(m--){
int x,y;
cin>>x>>y;
cout<<size[p[x][y]]<<endl;
}
return 0;
}
3.讲解
1.连通块统计
每次dfs即表示新建建一个连通块。一定要将信息记录完整。包含点与连通块的关系,连通块大小。
2.判断条件
能够进入下个点的关键,有的时候可能描述更加复杂,但要总结当前点与下一个点的关系。
3.vis[i][j]=1
有些题目我们没有标记起点,因为最后会遍历到,但是这个细节大家要注意。第一次进入dfs时就已经进行统计,因此最后不能再重复统计。
深度优先搜索-dfs算法到这里就结束了。