深度优先搜索+剪枝入门

接触深搜好长一段时间了,从最开始的八皇后问题一脸懵,到现在能写一点简单的深搜,感觉进步还是有一点点,剪枝还是基本想不到有效的剪枝方法,只会一点简单的剪枝,今天整理一下我学的深搜。
搜索这个东西抽象性太强,有人说深搜就是不撞南墙不回头,我觉得很有道理,是一种盲目搜索,深搜的实现一般是用栈或者说是递归来实现的 ,显然递归又是一个很抽象的东西,人脑这种单线程的东西做不了那么复杂的事情,还得让机器帮我去搜索。
说说深搜的模板吧

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;
		}
	}
}

猜你喜欢

转载自blog.csdn.net/yezi_coder/article/details/104231607
今日推荐