DFS也学了有一段时间了,虽然相关的题目还是有点写不出来,但是其基本思想还是可以总结一二的。
DFS,深度优先搜索,通俗的说,就是 “一条道走到黑” ,“不撞南墙不回头”。它可以把所有可能的类型枚举出来,是不具有最短路效应的。
DFS算法中,str[N]数组是用来存放各元素的状态的,即 用来判断是否被遍历过 。其目的是,不再重复的遍历。
DFS中还有个词叫做 回溯 ,它就是追溯到当前支路上最深的位置,无法再继续深追,然后原路返回,并把当前的str数组恢复到原来的状态的过程。
(并非每个DFS需要回溯,想判断是否需要回溯,就看是否有必要重复遍历,若没必要重复,就可以不需要str回溯了)
首先,要确定枚举的顺序,即 是依据什么来分类的 。(这个很重要,它可以影响你整个代码的运行效果)
然后,就是画搜索图。(这个的话,只是为了像我一样的初学者更好理解DFS这个算法)
大致思路确定后就开始去实现了。
DFS大部分都是模板题。但是模板分很多种 ,我道行不够,暂时总结不了,等我再去练习一段时间吧。下边再加几个经典的例题,加深记忆。
1.0 Acwing.842 排序数字
这题我以它三位数确定的顺序来枚举。
如,我先确定第一个数,它可能为1,可能为2,可能为3,分为了三个分支(下图)。
若第一个数为1,那第二个数可能为2,也可能为3,这就又分成了两个分支。
后面依此类推。
它的搜索图长这个样子
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 10;
int n;
int path[N],str[N];//path数组用来记录排列的顺序,str数组用来记录状态(是否经过了)
void dfs(int u)
{
if(u==n)//返回条件
{
for(int i=0;i<n;i++)
cout<<path[i]<<" ";
cout<<endl;
return;
}
for(int i=1;i<=n;i++)
{
if(!str[i])
{
str[i]=1;//记录已经历的点
path[u]=i;
dfs(u+1);
str[i]=0;//复原状态
}
}
}
int main()
{
cin>>n;
dfs(0);//从0开始
return 0;
}
2.0 Acwing.843 n-皇后问题
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 11;
int n,str[N][N];
char q[N][N];
int h[N],d[N],f[N];//一行、主对角线、副对角线
void dfs(int u)
{
if(u==n)
{
for(int i=0;i<n;i++)
puts(q[i]);
cout<<endl;
return;
}
for(int i=0;i<n;i++)
{
if(!h[i]&&!d[u+i]&&!f[n-u+i])
{
q[u][i]='Q';
h[i]=d[u+i]=f[n-u+i]=1;
dfs(u+1);
h[i]=d[u+i]=f[n-u+i]=0;
q[u][i]='.';
}
}
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
q[i][j]='.';
}
dfs(0);
}
3.0 牛客小白月赛 70 - C 小d和超级泡泡堂
登录—专业IT笔试面试备考平台_牛客网
Input
4 4
..!.
.@.#
!##!
#!!!
Output
2
这题我以一个点的上下左右进行分类。
它的搜索图即为
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1111;
char q[N][N];//存放地图
int n,m,ans;
int str[N][N];//记录状态
int x1[]={0,0,-1,1};
int y1[]={1,-1,0,0};//x1,y1用来控制上下左右
void dfs(int u,int r)
{
int x,y;
str[u][r]=1;//标记好已遍历的点
for(int i=0;i<4;i++)
{
x=u+x1[i];//在整个dfs函数中,u、r对应的是当前的点
y=r+y1[i];//x、y对应的则是下一个点的位置
if(x>=0&&x<n&&y<m&&y>=0&&!str[x][y]&&q[x][y]!='#')
{//在地图里、没有被标记过、不是石头
dfs(x,y);
if(q[x][y]=='!')//若为草,则ans直接加一
ans++;
}
}
}
int main()
{
int a,b;
cin>>n>>m;
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
cin>>q[i][j];
if(q[i][j]=='@')//找@所在的位置
{
a=i;
b=j;
}
}
}
dfs(a,b);//从@点开始
cout<<ans<<endl;
return 0;
}
这是一个 连通块 类型的问题,与之前的全排问题有所区别。
4.0 牛客-璐神看岛屿
题目描述
璐神现在有张n*m大小的地图,地图上标明了陆地(用"#"表示)和海洋(用"."表示),现在璐神要计算这张地图上岛屿的数量。
已知岛屿是由陆地的连通块组成,即一块陆地的上、下、左、右,左上,右上,左下,右下有其他陆地,则构成连通块,以此类推。
此外,岛屿的详细定义如下:
1、岛屿的周围必须全是海洋。
2、如果连通块有任意区域在地图边界,则该连通块不是岛屿。
Input
3 3
...
.#.
...Output
1
*样例说明:只有中间的1块陆地是岛屿,所以岛屿数=1
Input
3 3
#..
.#.
...Output
0
*样例说明:中间的连通块有区域在边界,所以不是岛屿,岛屿数=0。
这题的题目意思很重要。要重点看岛屿的定义,以及样例。
一个连通块有8个方向,这都属于它的区域。
地图边界不能存在连通块,否则则不属于岛屿。
*continue:跳过此次循环,进行下一次循环
*break:跳出一个循环体或者完全结束一个循环
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 222;
char map[N][N];
int n,m,ans,sum;
int str[N][N];//记录状态
int x1[]={0,0,-1,1,-1,-1,1,1};
int y1[]={-1,1,0,0,-1,1,-1,1};//上、下、左、右、左上、左下、右上、右下
void dfs(int u,int v)
{
int x,y;
if(u==0||u==n-1||v==0||v==m-1) sum=1;//在边界时
str[u][v]=1;
for(int i=0;i<8;i++)//八个方向
{
x=u+x1[i];
y=v+y1[i];
if(x>=0&&x<n&&y>=0&&y<m&&!str[x][y]&&map[x][y]=='#')
dfs(x,y);//在地图上、没有遍历过、是一个陆地
}
}
int main()
{
cin>>n>>m;
for(int i=0;i<m;i++)
cin>>map[i];
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
sum=0;
if(map[i][j]!='#'||str[i][j]) continue;//遇到海洋或者已经遍历过的点,直接跳过
dfs(i,j);
if(!sum)//不在边界
ans++;
}
}
cout<<ans<<endl;
}