【蓝桥杯】2015决赛A组 5 穿越雷区(深度优先搜索dfs、广度优先搜索bfs)

历届试题 穿越雷区

问题描述

X 星的坦克战车很奇怪,它必须交替地穿越正能量辐射区和负能量辐射区才能保持正常运转,否则将报废。
某坦克需要从 A 区到 B 区去(A,B 区本身是安全区,没有正能量或负能量特征),怎样走才能路径最短?
已知的地图是一个方阵,上面用字母标出了 A,B 区,其它区都标了正号或负号分别表示正负能量辐射区。

例如:

A + - + -
- + - - +
- + + + -
+ - + - +
B + - + -

坦克车只能在水平或垂直方向上移动到相邻的区。

输入格式

输入第一行是一个整数 n n n,表示方阵的大小, 4 ≤ n < 100 4\le n <100 4n<100
接下来是 n n n 行,每行有 n n n 个数据,可能是 A,B,+,- 中的某一个,中间用空格分开。
A,B 都只出现一次。

输出格式

要求输出一个整数,表示坦克从A区到B区的最少移动步数。如果没有方案,则输出-1

样例输入

5
A + - + -
- + - - +
- + + + -
+ - + - +
B + - + -

样例输入

10



—— 初窥门径 ——


本题是在学习 搜索算法 后必须刷的经典例题,这道题的经典之处在于两点:

  1. 改变了传统走迷宫的固定模式。
  2. 两种搜索算法都可解。

下面一一介绍与分析这两种不同的解法。



—— 大梦初醒:广度优先搜索 ——


通常情况下看到 “求最短路径” 字样的题时,我们应该想到的第一个算法都是 bfs。这个算法的特点是不一定会将整个地图遍历完,而是直接在一个队列中进行迭代操作进而找到最短路径,并且没有递归调用函数,因此在求最短路径的时候其时间开销相对还是较短的。

在本题中,我们的解题思路与其他的走迷宫算法基本一致,唯一不同的在于 “必须交替地穿越正能量辐射区和负能量辐射区才能保持正常运转”,即我们的算法中,在判断下一个点能否走时有两个点要被考虑:

  • 该点是否走过?
  • 该点的能量是否与当前点的能量相异?

只有这两个条件都满足时才能走。因此,可直接将这两个条件加入 bfs 中并作为移步条件,据此可得到以下代码:

#include<iostream>
#include<queue>
using namespace std;

const int MAX=105;
int n;
char map[MAX][MAX];
bool vis[MAX][MAX]; 
int go[][2]={{1,0},{0,-1},{0,1},{-1,0}};//四个可行走方向 

struct Point
{
	int x,y,step;
	Point(int a,int b,int c)
	{ x=a,y=b,step=c; }
};

void bfs(int x,int y)
{
	queue<Point> q;
	Point p(x,y,0);
	q.push(p);
	vis[x][y]=true;
	while(!q.empty())
	{
		Point temp=q.front();q.pop();
		for(int i=0;i<4;i++)
		{
			int new_x=temp.x+go[i][0],new_y=temp.y+go[i][1];
			if(new_x>=1 && new_x<=n && new_y>=1 && new_y<=n
			&& !vis[new_x][new_y] && map[temp.x][temp.y]!=map[new_x][new_y])
			{
				if(map[new_x][new_y]=='B'){ 
					cout<<temp.step+1<<endl;
					return; 
				}
				vis[new_x][new_y]=true;		//标记为已走 
				q.push(Point(new_x,new_y,temp.step+1));
			}
		}
	}
	//如果队列中所有点都走完了也没能找到一个最短的移动方案就直接输出-1
	cout<<-1<<endl;
}

int main()
{
	int start_x,start_y;
	cin>>n;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			cin>>map[i][j];
			if(map[i][j]=='A')
				start_x=i,start_y=j;
		}
	bfs(start_x,start_y);
	return 0;
}


—— 柳暗花明:深度优先搜索 ——


深度优先搜索算法的特点是以递归的方式对地图进行遍历,其存在回溯的过程,因此会遍历完图中的所有情况。而我们要解本题,正好要用到 dfs 算法的这一特性。由于 dfs 算法会遍历完所有的情况,故我们的解题思路就是直接暴力搜索得到所有的结果,然后取出其中的最小值。虽然说遍历完所有的情况看起来确实很棒,但实际上这样的下场是极易超时。也就是说对题中所给的数据范围非常敏感。

本题中的方阵最大会达到 99,这个范围对于 dfs 算法而言显然是非常大的,这在程序的 dfs 过程中会使得递归树过于深,从而导致超时的出现。因此就必须对dfs算法进行剪枝。

剪枝的主要思路是提前截断。什么意思?比如说某一次遍历得到了从起点到终点的步数为 12,当然,此时并没有完全结束,dfs 还会继续执行下去以寻求更小的答案。于是开始回溯,并往其他路线搜索。假设当前搜索到了某个点,且此时的步数为 13,那么就意味着再从这个点继续搜索下去就算能够找到终点出去,那这条路线的长度也一定会大于等于 13,这样的搜索显然是无意义的,因为已经找到了一个比 13 更小的路线。因此,我们的提前截断就是指在当前某个点的步数大于了之前已经得到了的某个步数后,就直接回溯,从而降低内存开销并加快搜索的速度。

基于上述思路,下面直接给出用 dfs 求解本题的完整代码:

#include<iostream>
using namespace std;

const int MAX=105;
int n,ans=100000000; 
char map[MAX][MAX];
bool vis[MAX][MAX]; 
int go[][2]={{1,0},{0,-1},{0,1},{-1,0}};//四个可行走方向 

void dfs(int x,int y,int step)
{
	if(step>=ans) return;	//剪枝:没必要再走下去了,反正即使可达也不会是最短路径了
	if(map[x][y]=='B')
	{
		ans=step;
		return;
	} 
	vis[x][y]=true;			//标记为已走 
	for(int i=0;i<4;i++)
	{
		int now_x=x+go[i][0],now_y=y+go[i][1];
		if(now_x>=1 && now_x<=n && now_y>=1 && now_y<=n
		&& !vis[now_x][now_y] && map[x][y]!=map[now_x][now_y] )
			dfs(now_x,now_y,step+1); 
	}
	vis[x][y]=false;		//回溯过程中还原 
}

int main()
{
	int start_x,start_y; 
	cin>>n;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			cin>>map[i][j];
			if(map[i][j]=='A')
				start_x=i,start_y=j;
		}
	dfs(start_x,start_y,0);
	if(ans==100000000) cout<<-1<<endl;
	else cout<<ans<<endl;
	return 0;
}

END


  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

theSerein

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值