初识宽度优先搜索(BFS)(一)

P3818 小A和uim之大逃离 II

瞬间,地面上出现了一个 HH 行 WW 列的巨幅矩阵,矩阵的每个格子上要么是空地 . 或者障碍 #。

他们起点在 (1,1)(1,1),要逃往 (H,W)(H,W) 的出口。他们可以一次向上下左右移动一格,这个算一步操作。不过他们还保留着上次冒险时收集的魔液,一口气喝掉后可以瞬移到相对自己位置的 (D,R)(D,R) 向量;也就是说,原来的位置是 (x,y)(x,y),然后新的位置是 (x+D,y+R)(x+D,y+R),这个也算一步操作,不过他们仅能至多进行一次这种操作(当然可以不喝魔液)。

这个地方是个是非之地。所以他们希望知道最小能有几步操作可以离开这个鬼地方。不过他们可能逃不出这个鬼地方,遇到这种情况,只能等死,别无他法。

输入格式
第一行个整数,H,W,D,RH,W,D,R,意义在描述已经说明。

接下来 HH 行,每行长度是 WW,仅有 . 或者 # 的字符串。

输出格式
请输出一个整数表示最小的逃出操作次数。如果他们逃不出来,就输出 -1−1。

输入输出样例
输入

3 6 2 1
…#…
…##…
…#…
输出
5
输入
3 7 2 1
…#…#.
.##.##.
.#…#…
输出
-1

思路:

如果没有药水这个限制就是裸的BFS走迷宫。

原先走迷宫需要用一个数组step[x][y]来记录到(x,y)的最少步数,这里只需要再添加一维记录用药和未用药的情况即可。

我们用step[x][y][0]表示走到(x,y)且没有喝药的最少步数,用step[x][y][1]表示走到(x,y)且已经喝药的最少步数。

具体过程:
遍历到一个点(简称遍历点),枚举上下左右的点(简称到达点),如果到到达点时,按迷宫的方式处理,在判断是否吃药,没吃药,则按迷宫方法处理,当然要注意三维的变化。

代码如下:

#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;

#define MAXN 1005//若是先检查数组下标是否越界的话,1005足以,若nx + d在检查之前,则要考虑下标越界的情况。
struct P{
	int x,y,u;
};//这里用结构体来实现三维,若是二维的话,用pair表示状态,使用typedef会更加方便一些。
char maze[MAXN][MAXN];
int h,w,d,r;
int step[MAXN][MAXN][2];//前两维记录每个地方的步数,后一维记录有药和没药的步数情况
int dx[5] = {0,0,-1,1},dy[5] = {1,-1,0,0};
bool check(int x,int y)
{
	return x <= h && x >= 1 && y <= w && y >= 1 && maze[x][y] != '#';
} 
void bfs()
{
	memset(step,-1,sizeof(step));//头文件为cstring
	queue<P> que;
	que.push((P){1,1,0});
	step[1][1][0] = 0;
	while (que.size() && step[h][w][0] == -1 && step[h][w][1] == -1)
	{
		P p = que.front();
		que.pop();
		for (int i = 0;i < 4;i ++){
			int nx = p.x + dx[i],ny = p.y + dy[i];
			if (check(nx,ny) && step[nx][ny][p.u] == -1)//为什么不把所有条件写入,因为需要传参,这样就不能达到简洁的效果
			{
				que.push((P){nx,ny,p.u});
				step[nx][ny][p.u] = step[p.x][p.y][p.u] + 1;
				if (check(nx + d,ny + r) && p.u == 0 && step[nx + d][ny + r][1] == -1)//这里应该先判断数组下标是否越界,否则,数组可能会溢出。
				{
					que.push((P){nx + d,ny + r,1});
					step[nx + d][ny + r][1] = step[nx][ny][0] + 1;
				}
			}
		}
	}
}
int main()
{
	scanf("%d%d%d%d",&h,&w,&d,&r);
	for (int i = 1;i <= h;i ++)
		scanf("%s",maze[i] + 1);
	bfs();
	if (step[h][w][0] == -1 && step[h][w][1] == -1)
		puts("-1");
	else
		printf("%d",min(step[h][w][0] == -1?1<<30:step[h][w][0],
						step[h][w][1] == -1?1<<30:step[h][w][1]));
	return 0;
}

注:

1.check函数时,判断数组下标是否越界一定要放在前边哦。

2.1<<30了解更多
  经查阅,<<是位运算符号,代表把1的二进制表示左移30位,左移一位(即在原来的数后面加一个0)相当于乘以2,左移30位应该是相当于乘以2的30次方。
同理,也有>>右移运算,又移一位相当于除2。

3.关于C语言中scanf输入的缺陷

scanf 输入字符串%s时,它的特性是 从第一个非空白字符开始读取,直到空白字符为止(不包括空白字符),所以空格会作为截断符,在输入带有空格符的字符串时,通常选择gets而不是scanf。

案例如下(迷宫的基本格式):
经典案例(传送门)

//true
scanf("%d%d",&n,&m);//也可以写成scanf("%d %d",&n,&m);
for (int i = 0;i < n;i ++)
	scanf("%s",maze[i]);
//false
scanf("%d%d",&n,&m);
for (int i = 0;i < n;i ++)
{
	for (int j = 0;j < m;j ++)
	{
		scanf("%c",&maze[i][j]);
	}
	//getchar();加上getchar吸收换行符就可以正确输入了。
}

为什么scanf("%d%d",&n,&m);与scanf("%d %d",&n,&m);输入结果一样?
调用scanf()函数的一般格式如下:
scanf(“格式字符串”,输入项地址表);

其中,“格式字符串”可以包含三种类型的字符:格式指示符、空白字符(空格、跳格键、回车键等)和非空白字符(又称为普通字符)。格式指示符用来指定数据的输入格式;空白字符作为相邻两个输入数据的默认分隔符;非空白字符在输入有效数据时必须按原样一起输入。(详细点击👈)

//false
#include <stdio.h>
int main(){
char door[5];
int i;
printf("请选择门号:");
scanf("%d",&i);
printf("请输入第%d号门的权限:",i);
scanf("%c",&door[i]);
return 0;
}

因为你前面输入门号的时候需要按下回车键(\n)来转入下一个scanf,也就是说你输入的是一个数字+一个字符。

如果要第二个scanf用%c得到自己输入的字符,可在第一个scanf和第二个scanf之间加入一个getchar来取掉输入数字时输入的字符。

之所以用 空格%c%s能够不需要getchar掉跟随数字进入缓冲区的那个字符是因为 scanf的格式匹配字符串中空格表示空白字符的占位符(任何空白字符都会自动匹配上这个空格,所以相当于这个空白字符被getchar掉了)

而%s则会忽略前导的空白字符(刚好这个空白字符在取掉数字后就位置就处于第一位了,也被忽略掉了)

4.空字符和空白字符(👈原文)

空格、制表符、换行符(创建新行)、回车符、换页符、垂直制表符称为“空白字符”,因为它们与打印页上的单词和行之间的空格一样都是起到方便阅读的作用。 标记由空白字符和其他标记分隔(划分边界),如运算符和标点。 在分析代码时,C 编译器将忽略空白字符,除非您将它们用作分隔符或者字符常量或字符串文本的组成部分。 使用空白字符可以让程序更易于阅读。 请注意,编译器也将注释视为空白。

C语言的空字符是在字符串结尾系统自动加上的\0,以让系统识别出一个字符内串的结尾。如:字符串“china”。在系统内是以“china\0”储存的。

在这里插入图片描述
BIN、OCT、HEX、DEC分别代表二、八、十六、十进制~
binary 二进制的
octal 八进制的
hexadecimal 十六进制的
decimal 十进制的


迷宫的基本格式

#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
//使用pair表示状态时,使用typedef会更加方便一些。 
typedef pair<int,int> P;
const int MAX_N = 105;
const int MAX_M = 105;

int n,m;
int sx,sy;//起点坐标 
int gx,gy;//终点坐标 
char maze[MAX_N][MAX_M];//表示迷宫的字符串的数组 
int d[MAX_N][MAX_M];//各个位置的最短距离的数组 
int dx[4] = {0,0,1,-1},dy[4] = {1,-1,0,0};

int bfs()
{
	//把所有的位置初始化为-1 
	memset(d,-1,sizeof(d));
	queue<P> que;//利用队列来表示状态,根据提议来判断状态的格式。
	que.push(P(sx,sy));
	d[sx][sy] = 0;
	while (que.size())//这里还可以写成 !que.empty() 
	{
		P p = que.front();
		que.pop();
		if (p.first == gx && p.second == gy)// 这里的代码也可以写到while语句中,为p.first != gx && p.second == gy。 
			break;
		for (int i = 0;i < 4;i ++)
		{
			int x = p.first + dx[i],y = p.second + dy[i];
			if (x < n && x >= 0 && y < m && y >= 0 && maze[x][y] != '#' && d[x][y] == -1)//这里可以写一个check函数来使代码简洁 
			{
				que.push(P(x,y));
				d[x][y] = d[p.first][p.second] + 1;
			}
		}
	} 
	return d[gx][gy];
}
int main()
{
	scanf("%d %d",&n,&m);
	getchar();
	for (int i = 0;i < n;i ++)
	{
		for (int j = 0;j < m;j ++)
		{
			scanf("%c",&maze[i][j]); 
			if (maze[i][j] == 'S')//起点
			{
				sx = i;
				sy = j; 
			} 
			if (maze[i][j] == 'G')//终点
			{
				gx = i;
				gy = j;
			}
		}
		getchar();//吸收缓冲区的换行符
	}
	int res = bfs();
	printf("%d\n",res);
	return 0;
}

总结

BFS算法针对迷宫问题的解法,并在基础上,解决更复杂的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值