初见安~这篇我们来讲讲深搜(DFS)
前文我们讲过了递归【这里是递推递归】,这里我们就要运用到啦~
所谓深搜,也顾名思义就是在深度上搜索,到了尽头则返回上一层,换一条路继续搜——也就是递归思想。
先看一道题了解一下吧:【中国邮递员问题】
对这道题就是很直白的dfs搜索目标。也正如题解里所言:很多dsf也可以用bfs实现,但与此同时也有很多是不能实现的。
比如搜索路径条数——
2N皇后
(本题出自计蒜客)
Description
给定一个 n*n 的棋盘,棋盘中有一些位置不能放皇后。现在要向棋盘中放入 n 个黑皇后和 n 个白皇后,使任意的两个黑皇后都不在同一行、同一列或同一条斜线(包括正负斜线)上,任意的两个白皇后都不在同一行、同一列或同一条斜线(包括正负斜线)上。问总共有多少种放法?n 小于等于 8。
Input
输入的第一行为一个整数 n,表示棋盘的大小。接下来 n 行,每行 n 个 0 或 1 的整数,如果一个整数为1,表示对应的位置可以放皇后,如果一个整数为 0,表示对应的位置不可以放皇后。
Output
输出一个整数,表示总共有多少种放法。
Sample Input 1
4
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
Sample Output 1
2
Sample Input 2
4
1 0 1 1
1 1 1 1
1 1 1 1
1 1 1 1
题解
了解题目后,我们可以有个大概的思路:黑白皇后之间无所谓冲突,所以我们只要分别处理好并标记位置确定同色皇后之间不冲突即可。那我们不妨大胆(不怕超时)尝试一下:先处理黑皇后(先白也可以),每放一个都四下检查一次有没有同色皇后;到了最后一行且皇后数量达到了2N时就开始找白皇后or跳出递归,已走过所有路径。
下面是代码及详解:
#include<bits/stdc++.h>
using namespace std;
int n,a[10][10],k=0;
int cnt=0;//cnt计数
bool in(int x,int y) //检查是否越界
{
return 1<=x&&x<=n&&1<=y&&y<=n;
}
void dfs(int i,int q)//黑标记2白标记3 ,i为到第几行了。
{
if(i>n) return;//越界
for(int j=1;j<=n;j++)
{
if(in(i,j)&&a[i][j]==1)//在(i,j)位置四下搜索是否遇得到已放皇后
{
bool flag=1;
for(int k=1;k<=i-1;k++)//第i行后面的确定是没放的,所以不必搜索
{
if(a[k][j]==q) flag=0;
}
//不用搜索同一行,因为放了就深入到下一行
int tx=i-1,ty=j+1;
while(in(tx,ty))//斜向搜索
{
if(a[tx][ty]==q) flag=0;
tx--;ty++;
}
tx=i-1,ty=j-1;
while(in(tx,ty))
{
if(a[tx][ty]==q) flag=0;
tx--;ty--;
}
if(flag)//通过了以上的检查操作
{
a[i][j]=q;
if(i==n&&q==2)//黑皇后放完了
{
dfs(1,3);
}
else if(i==n&&q==3) //搜索完毕,计数
{
cnt++;
}
else dfs(i+1,q);
a[i][j]=1;//递归循环,复原
}
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j];
dfs(1,2);
cout<<cnt<<endl;
return 0;
}
我知道还有个问题叫做8皇后问题。【和上述思路差不多,可以去试一下】
OK,我们再来看一道题:
等边三角形
(本题出自YCOJ)
Description
小白手上有一些小木棍,它们长短不一,小白想用这些木棍拼出一个等边三角形,并且每根木棍都要用到。 例如,小白手上有长度为 1,2,3,3 的4根木棍,他可以让长度为1,2 的木棍组成一条边,另外 2 跟分别组成 2 条边,拼成一个边长为 3 的等边三角形。小白希望你提前告诉他能不能拼出来,免得白费功夫。
Input
首先输入一个整数 n(3 ≤ n ≤ 200),表示木棍数量,接下来输入 n 根木棍的长度 p_i(1 ≤ p_i ≤ 10000)。
Output
如果小白能拼出等边三角形,输出"yes",否则输出"no"。
Sample Input 1
5
1 2 3 4 5
Sample Output 1
yes
Sample Input 2
4
1 1 1 1
Sample Output 2
no
题解
我们知道等边三角形就是三边相等。所以这道题我们可以凑(强行枚举?)出结果来。当然本题只需要返回是否存在解,所以找到了就可以了。
下面是代码及详解——
#include<bits/stdc++.h>
using namespace std;
int n,num[200],s=0;
bool flag=0;
void dfs(int a,int b,int c,int i)//三边长及用到了第几个木棍
{
if(flag==1) return;//已经找到解了就不用在找了
if(a==b&&b==c&&a!=0&&i==n+1)//如果找到了:
{
flag=1;
return;
}
if(i>n+1||a>s||b>s||c>s)//返回上一个,因为长度超过了
{
return;
}
dfs(a+num[i],b,c,i+1);//强行凑!(什么鬼……)
dfs(a,b+num[i],c,i+1);
dfs(a,b,c+num[i],i+1);
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>num[i];
s+=num[i];
}
if(s%3!=0)//如果连总长都不能三等分的话,就不用考虑了。
{
cout<<"no"<<endl;return 0;
}
else
{
s=s/3;//s变为要凑出来的边长度
dfs(0,0,0,1);
}
if(flag==1) cout<<"yes"<<endl;//flag标记
else cout<<"no"<<endl;
return 0;
}
最后,其实DFS有一个优化操作——迭代加深搜索。什么是迭代加深呢?就是搜索前先限定好深度搜,如果没搜到那么我们再更改深度。相当于是枚举答案。
画个图理解一下:假设我们枚举的深度一点点加深,那么搜索路径就是这样的:
只有当搜索深度为3的时候最后一个点才会被搜到。
这样搜看起来前面的点被重复搜了很多次,但其实效率多数时候是比一路搜到最深处的dfs要优秀一些的。
有一个不那么经典的迭代加深搜索题目:洛谷P2329 栅栏(fence8)。虽然是二分枚举的答案,但是也算是迭代加深的。
后期还有不少搜索的经典题目,欢迎到博客目录翻阅~
迎评:)
——End——