深度优先搜索(DFS)

前言:搜索有俩大几乎万能的法器:DFS与BFS

本文介绍DFS

BFS可以参考广度优先搜索(BFS)

1,算法特点:

DFS是一个莽夫,在遇到死路之前,他只会一根筋往前走,不回头(形象比喻)

所以算法特点就是每次一直往节点走下去,直到没有节点,就返回上一个节点看看还有没有其他节点,有就继续,没有就还是返回上上个节点,直到全部遍历完为止

2,基本思路:

bool vis[N];
void dfs(){
	if()return;//结束搜索的条件
	for( ; ;){
		if(vis[x])continue;//如果这个点已经在目前这个路径走过,跳
		vis[x]=1;//标记该点访问过
		dfs(x+1);//往深处dfs
		vis[x]=0;//出来就标记为没有访问过,因为我走了
	}
}

3,例题一:

 思路:很明显,就普通的写dfs肯定能得到答案,但是肯定会被TLE。

我们注意到:(假设起点(sx,sy),终点(ex,ey),要求时间T

1,从起点到终点最少需要abs(ex - sx) + abs(ey - sy)的时间,如果T比他小,不考虑

2,除去墙的数目count,如果T>n*m-count,那么我把所有格子走完时间也没到,不考虑

3,我们会发现,从起点到终点,如果起点坐标和是偶数,终点坐标和是偶数(俩个奇偶性相同),那么肯定要走偶数步,如果起点坐标和是偶数,终点坐标和是奇数(奇偶性不同),要走奇数步............所以奇偶性相同,走偶数步,不同奇数步,不符合,舍去,即(ex + ey)%2 ^ (sx + sy)%2) ^ (T % 2)(相同异或为0)

通过剪枝,我们才可避免TLE

AC代码

#include <bits/stdc++.h>
using namespace std;
#define ll     long long
const int INF = 0x3f3f3f3f;
const int N = 100;
		
char arr[N][N];
bool vis[N][N];
int n, m, T;
int sx, sy, ex, ey;
bool flag;
int way[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};//4个方向的基向量
		
void dfs(int x, int y, int t)
{
	if (flag)return;//如果已经找到符合结果,不用往下了
	if (x == ex && y == ey && t == T)
		{
			flag = 1;
			return;
		}
	if (t >= T)return;//时间过了也不用往下了
	for (int i = 0; i < 4; ++i)
		{
			int tx = x + way[i][0], ty = y + way[i][1];
			if (tx >= 1 && tx <= n && ty >= 1 && ty <= m && !vis[tx][ty] && arr[tx][ty] != 'X')//要求:坐标合法,没有标记过,这个点可以走
				{
					vis[tx][ty] = 1;
					dfs(tx, ty, t + 1);
					vis[tx][ty] = 0;
				}
		}
}
		
int main()
{
	while (cin >> n >> m >> T && n + m + T != 0)
		{
			memset(vis, 0, sizeof(vis));
			int count=0;
			for (int i = 1; i <= n; ++i)for (int j = 1; j <= m; ++j)
					{
						cin >> arr[i][j];
						if (arr[i][j] == 'S')
							{
								sx = i, sy = j;
							}
						else if (arr[i][j] == 'D')ex = i, ey = j;
						else if(arr[i][j]=='X')count++;
		
					}
			if (abs(ex - sx) + abs(ey - sy) > T||T>n*m-count||((ex + ey)%2 ^ (sx + sy)%2) ^ (T % 2))
				{
					cout << "NO" << endl;
					continue;
				}
			flag = 0;
			vis[sx][sy] = 1;//起点先标记为访问过
			dfs(sx, sy, 0);
			if (flag)
				{
					cout << "YES" << endl;
				}
			else
				{
					cout << "NO" << endl;
				}
		}
		
	return 0;
}

4,例题2:

思路:同样,你知道我要说什么,直接正面刚一定被TLE,那么我们思索哪里不必要

我们会发现,每个点可以获得的最大值是固定的,如果我们按常规,就好多求好多次,

我们想让他每个只求一次,那我们可以从大的点累积到小的点,但是我们不清楚谁是最优的较大(最大不一定最优) 

聪明的你一定会想到dfs是可以从头深入到尾,那我们可以让他深入到最底层,再求(这样我们求出最底层的maxn(就是他自己的值),返回,我们就求出第二最底层的maxn,即maxn[x][y] = max(maxn[x][y], arr[x][y] + dfs(tx, ty));

而且,我们dfs只要保证能求出我当前要的maxn[x][y],后面有需要,直接返回这个值即可

#include <bits/stdc++.h>
using namespace std;
#define ll     long long
const int INF = 0x3f3f3f3f;
const int N = 200;

int way[4][2] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};

ll arr[N][N];
ll maxn[N][N];
int n, k;

ll dfs(int x, int y)
{
	if (maxn[x][y])return maxn[x][y];//初始化为0,如果你值不为0,说明其最大值已经求过,直接返回
	maxn[x][y] = arr[x][y];//首先对于他自己(因为可能没有比他大的了
	for (int i = 0; i < 4; ++i)for (int j = 1; j <= k; ++j)
			{
				int tx = x + way[i][0] * j, ty = y + way[i][1] * j;//以基向量与一步多长的j来决定去哪
				if (tx >= 0 && tx < n && ty >= 0 && ty < n && arr[x][y] < arr[tx][ty])
					{
						maxn[x][y] = max(maxn[x][y], arr[x][y] + dfs(tx, ty));//我们最优走,只会一直往更大的值去,等他算完返回来,求可以求我当前要的maxn[x][y]
					}                                       //arr[x][y] + dfs(tx, ty),不漏arr[x][y],因为dfs只是深层的点,不包括当前这个点
			}
	return maxn[x][y];//最后return
}

int main()
{

	while (cin >> n >> k && (k != -1 || n != -1))
		{
			for (int i = 0; i < n; ++i)for (int j = 0; j < n; ++j)cin >> arr[i][j];

			cout << dfs(0, 0) << endl;
			memset(maxn, 0, sizeof(maxn));
		}
	return 0;
}

 5,例题3,状压优化dfsP1433 吃奶酪

思路:状态压缩,我们当进过相同点(且最后的点一样时,不同遍历会得到不同结果),但是,显然,我们只要最优的那个,那么我们发现数据n小于15,我们可以利用二进制表示点的存在状况,如01000101,就是第1,3,7个点有出现(1表示出现,0步出现),(理论如果用32个点),这样就能实现剪枝

#include <bits/stdc++.h>
using namespace std;
#define ll     long long
const int INF = 0x3f3f3f3f;
const int N = 21;

vector<pair<double, double>>vc;//都是实数,不要有开成整型的了,特别是这个容器
double dis[N][N];//dis[i][j]记录i,j俩点的距离
double lu[(1 << 16) + 100][20];
bool vis[N];
double ans;
void dfs(int x, int way, double t, int n)//x表示当前点,way为二进制存储目前记录的点,t记录总路径,n表示剩余几个点没走
{
	if (t >= ans)return; //超过当前最优就直接跳,最普遍的剪枝之一
	if (n == 0)
		{
			ans = min(ans, t);
			return;
		}
	for (int i = 1; i < (int)vc.size(); ++i)
		{
			if (!vis[i])
				{
					int tmp = way + (1 << (i - 1));
					if ((!lu[tmp][i] || lu[tmp][i] > t + dis[x][i]) && t + dis[x][i] < ans)//!lu[tmp][i]是保证第一次进入赋予初始值
						{
							lu[tmp][i] = t + dis[x][i];
							vis[i] = 1;
							dfs(i, tmp, t + dis[x][i], n - 1);
							vis[i] = 0;

						}
				}
		}

}

int main()
{
	int n;
	double x, y;//实数
	cin >> n;
	vc.push_back({0.0, 0.0});
	for (int i = 1; i <= n; ++i)
		{
			cin >> x >> y;
			vc.push_back({x, y});
			for (int j = 0; j < i; ++j)//预处理点之间的距离,避免dfs多次重复计算
				{
					dis[i][j] = dis[j][i] = sqrt(fabs(vc[i].first - vc[j].first) * fabs(vc[i].first - vc[j].first) + fabs(vc[i].second - vc[j].second) * fabs(vc[i].second - vc[j].second));
				}
		}
	ans = INF * 1.0;//初始极大
	vis[0] = 1;//起点标记访问过
	dfs(0, 0, 0.0, n);
	printf("%.2lf", ans);
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值