我最近几日一直想写一遍博客但由于是自己人生中的第一篇博客就一直在想要写啥
今日突然想到我的一位恩师说过:“学好搜索呀,走遍天下都不怕!”所以今天我来写一篇关于DFS的博客
(全篇有一点长,请各位耐心看完)
DFS的基本概念
DFS全称Depth First Search翻译过来就是深度优先搜索
它是一种用来遍历或搜索树或图的算法。沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v所在的整条边都已被搜寻或者搜寻节点不满足条件时,搜索将回溯到发现v节点的那条边上的初始起点。整个过程反复进行直到所有节点被访问为止。
这种算法最糟糕的情况就是时间复杂度为O(!n)
算法思想:搜索与回溯
在刚刚我们提到了回溯,那么回溯又是什么呢?
按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
回溯算法是一个很抽象的东西,但是所有的回溯算法都可以抽象成一个树状结构,可以将其抽象成一个n叉树问题。如果满足递归的条件,树枝可以无限增加,直到找到所需要数据为止,如果不满足,树枝则会折断。树的深度取决于要搜索问题的层数,树的宽度取决于每个节点处理集合的大小。
直接上图演示一下
这里我只找了八个节点来写就是举个例子
可运用的问题主要有五大类:组合问题,切割问题,子集问题,排列问题,棋盘问题这五种基本问题。
- 组合问题:N个数里面按一定规则找出k个数的集合
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 棋盘问题:N皇后,解数独等等
DFS的模板
这里我给一道模板题迷宫把dfs的模板看懂了的话可以尝试做一下这道题去巩固一下
dfs的模板如下
void dfs(int x,int y)
if(终止条件){
//按题意写终止后干什么
return;
}
for(int i=1;i<=4;i++){//可以是八联通 因为这题是四联通所以拿四联通举例
int dx=x+mv[i][0],dy=y+mv[i][1];
if(防止越界,重复的条件和判断是否有障碍物){
continue;
}
vis[dx][dy]=true;//标记
//部分题如全排列问题这里要定义一个a数组来储存当前的i
dfs(dx,dy);//继续搜
vis[dx][dy]=false;//取消标记
}
return;//这里一定要return,否则可能报错
}
!!!
注意三个判断可以分开写或者写在一个if语句里(如上)but判断是否越界一定要写在最前面(恩师的尊尊告诫)
说了这么多了接下来我来展示两道非常好的DSF题
1.
八皇后https://www.luogu.com.cn/problem/P1219https://www.luogu.com.cn/problem/P1219
题如下
这题其实不难想就是一道裸的深搜题。可是看出来了怎么写代码却是一个大问题。
由于皇后有很强的嫉妒心眼里容不下其她的皇后不然就会急眼,我们要想办法怎样才能让皇后走的横的位置,竖的位置还有斜的位置都只能有一个皇后。
这个就是我们在这道题中的主要思路。如果在准备放置皇后的时候发现,这一行都没有合适的位置,那么我们就回溯:退回上一状态,重新来过。
为了让皇后们可以吃“后悔药”,我们就必须给她们留下后路——曾经打过标记的地方都撤销掉,让她们有路可回。
思路都想好了那么就直接上代码
#include<bits/stdc++.h>
using namespace std;
long long n,ans,a[15],cnt;
bool l[15],djx1[15],djx2[15];
void cout_()
{
for(int i=1; i<=n; i++)
{
cout<<a[i]<<" ";
}
cout<<"\n";
}
void dog(int i)
{
if(i>n)
{
if(ans<3)
{
cout_();
}
ans++;
return ;
}
for(int j=1; j<=n; j++)
{
if(l[j]==0&&djx1[i+j]==0&&djx2[i-j+n]==0)
{
l[j]=1;
djx1[i+j]=1;
djx2[i-j+n]=1;
a[i]=j;
dog(i+1);
l[j]=0;
djx1[i+j]=0;
djx2[i-j+n]=0;
}
}
}
int main(){
cin>>n;
dog(1);
cout<<ans<<endl;
return 0;
}
我们也是直接获得一颗小星星
2.
取数游戏
https://www.luogu.com.cn/problem/P1123#submithttps://www.luogu.com.cn/problem/P1123#submit
这题基本看一眼也知道是深搜的题,这是有些不一样的是它不是让你去找走什么地方的方法数或让你在数组里面找一些数去排列,它让你在数组里去找若干个数字,使得取出的任意两个数字不相邻这里有点难想其他的倒也没啥,上代码:
#include <iostream>
using namespace std;
int n,m,Q,ans,a[10][10];
const int ii[] = {0,1,2, 4, 5, 8, 9,10,16,17,18,20,21,32,33,34,36,37,40,41,42};
const int jj[] = {0,3,7,14,15,28,31,31,56,59,63,62,63,48,51,55,62,63,60,63,63};
const int lim[] = {0,1,2,4,7,12,20};
void dfs(int x, int sum, int lst) {
if(x > n) {
if(sum > ans)
ans = sum;
return;
}
for(int i=0; i<=lim[m]; i++) {
if(ii[i] & lst) continue;
int tmp = sum;
for(int j=0; j<m; j++)
if((1<<j)&ii[i])
tmp += a[x][j+1];
if(tmp >= sum)
dfs(x+1,tmp,jj[i]);
}
}
int main() {
cin>>Q;
for(int q=1; q<=Q; q++) {
ans = -1000000000;
cin>>n>>m;
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
cin>>a[i][j];
dfs(1,0,0);
cout<<ans<<endl;
}
return 0;
}
然后我们又获得了一颗小星星
这里我再推荐几道好题下去可以去做一下巩固基础,提升能力。
P6207 [USACO06OCT] Cows on Skates G
P2919 [USACO08NOV] Guarding the Farm S
停一下 你不会以为到这里DFS就讲完了吧,当然没有,这里我将补充一点DFS的分支
FLOOD FILL的递归填充方法
flood fill又称洪水填充
从一个起始节点开始把附近与其连通的节点提取出或填充成相同的物质,直到封闭区域内的所有节点都被处理过为止,是从一个区域中提取若干个连通的点与其他相邻区域区分开(或分别填充成相同物质)的经典算法。
有点难理解所以我找了下面两图
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,m,ans;
char land[N][N];
bool st[N][N];
int dx[]={-1,0,1,0,1,1,-1,-1};
int dy[]={0,1,0,-1,-1,1,-1,1};//定义两个位置移动数组
void dfs(int xx,int yy)
{
for(int i=0;i<8;i++)//这里拿八联通来举例子
{
int x=xx+dx[i],y=yy+dy[i];//更新一下当前位置
if((x>!1&&x<n&&y>1&&y<m)!st[x][y]&&land[x][y]=='障碍物')//先判断越界,!st[x][y]判断当前位置有没有被走过,最后判断有没有障碍物
{
把当前位置填充成题目要求的内容
st[x][y]=true;//把当前位置标记为走过
dfs(x,y); //再dfs一遍
}
}
}
int main()
{
cin>>n>>m;//输入它是一个几×几的方格
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>land[i][j];//输入每个方格的内容
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(land[i][j]=='W'&&!st[i][j])//因为不确定是一个位置需要填充还是多个位置所以这里我们用双层for循环把每个位置都遍历一遍
{
dfs(i,j);//开始填充
}
cout<<"填充地方的数量或填充完后的方格"//主要看题目要求输出什么
return 0;
}
模板主要如上
下面,我拿一道用洪水填充做的题目来举个例子
#include <bits/stdc++.h>
using namespace std;
int a[32][32],b[32][32];
int dx[5]={0,-1,1,0,0}; //因为这题是四联通所以后面的四个分别是上下左右四个方向
int dy[5]={0,0,0,-1,1};//第一个表示不动,是充数的,但不可以不要不然你哪错了都不知道
int n,i,j;
void dfs(int p,int q){
int i;
if (p<0||p>n+1||q<0||q>n+1||a[p][q]!=0) return;//如果搜过头或者已经被搜过了或者本来就是墙的就往回
a[p][q]=1;//染色
for (i=1;i<=4;i++){
dfs(p+dx[i],q+dy[i]);//向四个方向搜索
}
}
int main(){
cin>>n;
for (i=1;i<=n;i++)
for (j=1;j<=n;j++){
cin>>b[i][j];
if (b[i][j]==0) a[i][j]=0;
else a[i][j]=2;
}
dfs(0,0);//搜索 从0,0开始搜
for (i=1;i<=n;i++){
for (j=1;j<=n;j++)
if (a[i][j]==0) cout<<2<<' ';//如果染过色以后i,j那个地方还是0,说明没有搜到,就是周围有墙,当然就是被围住了,然后输出2
else cout<<b[i][j]<<' ';
cout<<'\n';//换行
}
}
最后直接就AC了
我再推荐一道flood fill的题
P1596 [USACO10OCT] Lake Counting S
下去可以尝试做一下这道题
希望大家在看了我这篇博客之后对大家的DFS有一些帮助
最后
(点赞加关注,追更不迷路)
点一个赞吧,阿里嘎多