接触深搜好长一段时间了,从最开始的八皇后问题一脸懵,到现在能写一点简单的深搜,感觉进步还是有一点点,剪枝还是基本想不到有效的剪枝方法,只会一点简单的剪枝,今天整理一下我学的深搜。
搜索这个东西抽象性太强,有人说深搜就是不撞南墙不回头,我觉得很有道理,是一种盲目搜索,深搜的实现一般是用栈或者说是递归来实现的 ,显然递归又是一个很抽象的东西,人脑这种单线程的东西做不了那么复杂的事情,还得让机器帮我去搜索。
说说深搜的模板吧
void dfs()
{
if(退出条件)
{
...
return ;
}
if(越界或不合法状态)
{
...
return ;
}
if(特殊状态)//剪枝
{
return ;
}
for(开始搜索吧)//下一个状态
{
if(状态合法)
{
修改;
标记;
dfs();
(还原标记)//可以不用 加上就是回溯
}
}
}
最先接触dfs还是全排列问题
洛谷全排列
这个没什么好说的,就是简单的深搜
#include<bits/stdc++.h>
using namespace std;
int a[10];
int n;
bool vis[10];
void dfs(int x)
{
if(x > n)
{
for(int i = 1; i <= n; i++)
cout << " " << a[i];
cout << endl;
return ;
}
for(int i = 1; i <= n; i++)
{
if(!vis[i])
{
a[x] = i;
vis[i] = 1;
dfs(x+1);
vis[i] = 0;
}
}
}
int main()
{
cin >> n;
dfs(1);
}
上面的全排列是自动去重的,如果遇到那种需要去重的全排列怎么办?
我们看看poj的这道题
poj需要去重全排列
首先这道题用set肯定是超时了,然后就开始想办法
1、先将输入的字符串排序,然后dfs之后就自然是字典序了,实现也很简单,就是来一次sort就可以了
2、为了去重,我们保证相同字母的使用是有顺序的,比如bbbdj,对于字母d我们使用的顺序必须是使用第一个然后才能使用第二个,使用了第二个之后就必须不能再倒过来使用第一个,这样只按顺序使用就不会出现重复了,只会用b1…b2…b3的顺序,不然的话就有
b1b2b3 b1b3b2 b2b1b3 b2b3b1 b3b1b2 b3b2b1
的排法而程序会把他们看作是不同的排序方法
如何实现?
我们在同一层递归之中标记上一次使用了什么字母,保证不使用上一个字母
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
string st;
bool vis[207];
string s;
char a[207];
int num = 0;
void dfs(int x)
{
char c = 0;
if(x == s.size() )
{
for(int i = 0; i < s.size() ; i++)
cout << a[i];
cout << endl;
return;
}
for(int i = 0; i < s.size() ; i++)
{
if(!vis[i] && c != s[i])
{
vis[i] = 1;
a[x] = s[i];
c = s[i];
dfs(x+1);
vis[i] = 0;
}
}
}
int main()
{
cin >> s;
sort(s.begin() ,s.end() );
dfs(0);
}
然后是经典的八皇后问题
洛谷八皇后
思路:按行搜索,行数 = 皇后数
#include<iostream>
using namespace std;
int n, ans;
int lu[50];//左上到右下对角线 这个对角线行列之差相等
int ru[50];//右上到左下对角线 这个对角线行列之和相等
int lie[30];//列
int a[30];
void pr()
{
for(int i = 1; i <= n; i++)
cout << a[i] << " ";
cout << endl;
}
void dfs(int x)
{
if(x > n)
{
ans ++;
if(ans <= 3)
pr();//输出前三个解
return ;
}
for(int i = 1; i <= n; i++)
{
if(!lie[i] && !lu[x-i+n] && !ru[x+i])//lu加上n防止出现负数
{
lie[i] = 1;
lu[x-i+n] = 1;
ru[x+i] = 1;
a[x] = i;
dfs(x+1);
lie[i] = 0;//还原标记
lu[x-i+n] = 0;
ru[x+i] = 0;
}
}
}
int main()
{
cin >> n;
dfs(1);
cout << ans ;
}
接下来是一个变式
poj棋盘问题
这个问题有基本与八皇后相同,但是有一个特殊情况,k < n这个情况的处理
#include<iostream>
#include<cstring>
using namespace std;
char mp[9][9];
bool vis[9];
int n, k;
int ans;
int num;
void dfs(int x, int num)//num记录已放下的棋子数
{
if(num == k)
{
ans ++;
return ;
}
if(x > n)
return ;
for(int i = 1; i <= n; i++)
{
if( !vis[i] && mp[x][i] != '.')
{
vis[i] = 1;
dfs(x+1, num+1);
vis[i] = 0;
}
}
dfs(x+1, num);//不放 处理多余行
}
int main()
{
while(cin >> n >> k)
{
memset(vis,0,sizeof(vis));
ans = 0;
num = 0;
if(n == -1&& k == -1)
return 0;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
cin >> mp[i][j];
dfs(1,0);
cout << ans << endl;
}
}
再是一道简单的搜索题
poj入门搜索
这道题深搜广搜都可以,好像广搜还快那么一点点
第一个是深搜第二个是广搜的
看看深搜
//水洼为true 外面加一圈false
#include<iostream>
#include<queue>
bool mp[1001][1001];
int vis[1001][1001];
int ans = 0;
int n, m;
int xx[] = {0, 1, 1, 1, 0, -1, -1, -1};
int yy[] = {-1, -1, 0, 1, 1, 1, 0, -1};
using namespace std;
void dfs(int x, int y)
{
for(int i = 0; i < 8; i++)
{
int dx = x + xx[i];
int dy = y+ yy[i];
if(dx >= 1 && dx <= n && dy >= 1 && dy <= m && mp[dx][dy] && !vis[dx][dy])
{
vis[dx][dy] = 1;
dfs(dx,dy);
}
}
}
int main()
{
char c;
cin >> n >> m;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
{
cin >> c;
if(c == 'W')
mp[i][j] = true;
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
{
if(mp[i][j] && !vis[i][j])
{
dfs(i,j);
ans ++;
}
}
cout <<ans;
}
再看看广搜
//水洼为true 外面加一圈false
#include<iostream>
#include<queue>
bool mp[1001][1001];
int vis[1001][1001];
int ans = 1;
int n, m;
int xx[] = {0, 1, 1, 1, 0, -1, -1, -1};
int yy[] = {-1, -1, 0, 1, 1, 1, 0, -1};
using namespace std;
int main()
{
char c;
cin >> n >> m;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
{
cin >> c;
if(c == 'W')
mp[i][j] = true;
}
queue<int> x;
queue<int> y;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
{
if(mp[i][j] && !vis[i][j])//是水洼且没被搜过才进入
{
x.push(i);
y.push(j);
vis[i][j] = ans;
while(!x.empty() )
{
for(int k = 0; k < 8; k++)
{
int dx = x.front() + xx[k];
int dy = y.front() + yy[k];
if(dx >= 1 && dx <= n && dy >= 1 && dy <= m && mp[dx][dy] && !vis[dx][dy])
{
vis[dx][dy] = ans;
x.push(dx);
y.push(dy);
}
}
x.pop() ;
y.pop() ;
}
ans ++;
}
}
cout << ans - 1;
}
hdu深搜+剪枝
这题深搜不难,难在剪枝,如何剪枝写在注释了
#include<iostream>
#include<cstring>
using namespace std;
bool vis[9][9];
bool mp[9][9];
int xx[] = {0, 0, 1, -1};
int yy[] = {1, -1, 0, 0};
int n, m, t, flag;
int sx, sy, ex, ey;
void dfs(int x, int y, int step)
{
if(x == ex && y == ey && step <= t)//找到了 标记flag 退出
{
if(step == t)
flag *= 0;
return ;
}
if(!flag || step >= t)//找到了 或者 找不到了及时退出
return ;
for(int i = 0; i < 4; i++)
{
int dx = x + xx[i];
int dy = y + yy[i];
if(dx >= 1 && dx <= n && dy >= 1 && dy <= m && mp[dx][dy] && !vis[dx][dy])
{
vis[dx][dy] = 1;
dfs(dx, dy, step+1);
vis[dx][dy] = 0;
}
}
}
int main()
{
while(cin >> n >> m >> t)
{
flag = 1;
int cnt = 1;
memset(vis,0,sizeof(vis));
memset(mp,0,sizeof(mp));
if(n == 0 && m == 0 && t == 0)
return 0;
char c;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
{
cin >> c;
if(c == 'S')
sx = i, sy = j;
if(c == 'D')
ex = i, ey = j,mp[i][j] = true;
if(c == '.')
mp[i][j] = true,cnt++;
}
//for(int i = 1; i <= n; i++)
//{
// for(int j = 1; j <= m; j++)
// cout << mp[i][j] << " ";
// cout << endl;
//}
//cout << "sx=" << sx << " sy=" << sy << " ex=" << ex << " ey=" <<ey;
if(cnt >= t && t >= abs(ey-sy+ex-sx) && (t-abs(ey-sy+ex-sx))%2 == 0)//剪枝
//cnt 记录可以走的点的数 必须要不小于时间可能逃出
//第二个是曼哈顿距离 t必须不小于曼哈顿距离才可能逃出 曼哈顿距离是最小距离
//第三个 最强剪枝 在曼哈顿距离的情况下 你进行绕路 还是要绕回到曼哈顿线上来 你绕路增加的时间必然是偶数
//那么只有当你时间减去曼哈顿距离 剩下的时间为偶数才有可能在第t秒到达门口
dfs(sx, sy, 0);
if(flag)
cout << "NO" << endl;
else
cout << "YES" << endl;
}
}
hdu1175连连看 深搜+剪枝
剪枝写在注释了
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int xx[] = {0, 0, 1, -1};
int yy[] = {1, -1, 0, 0};
int n, m, q;
int mp[1007][1007];
int ques[57][4];
bool vis[1007][1007];
int x1, y1, x2, y2;
int flag;
void dfs(int x, int y, int t, int f)//t 转的次数 f上一次的方向
{
if(x == x2 && y == y2 && t <= 2)
{
flag = 0;
return ;
}
if(t==2 && (x-x2) != 0 && (y-y2) != 0)
return ;
//转了两次还没在一条线上 over 肯定没戏了
if(!flag)
return ;
if(t > 2)
return ;
for(int i = 0; i < 4; i++)
{
int dx = x + xx[i];
int dy = y + yy[i];
if(dx >= 1 && dx <= n && dy >= 1 && dy <= m && !vis[dx][dy] )
{
if(mp[dx][dy] == 0 || (dx == x2 && dy == y2) )
{
vis[dx][dy] = 1;
if(f != i && f != -1)//换方向了
dfs(dx,dy,t+1,i);
else
dfs(dx,dy,t,i);
vis[dx][dy] = 0;
}
}
}
}
int main()
{
while(cin >> n >> m)
{
memset(vis,0,sizeof(vis));
if(n == 0 && m == 0)
return 0;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
scanf("%d",&mp[i][j]);
cin >> q;
for(int i = 0; i < q; i++)
for(int j = 0; j < 4; j++)
cin >> ques[i][j];
for(int i = 0; i < q; i++)
{
memset(vis,0,sizeof(vis));
flag = 1;
x1 = ques[i][0];
y1 = ques[i][1];
x2 = ques[i][2];
y2 = ques[i][3];
if(mp[x1][y1] == mp[x2][y2] && mp[x1][y1] != 0)
dfs(x1, y1, 0, -1);
if(flag)
cout << "NO" << endl;
else
cout << "YES" <<endl;
}
}
}