一本通题解——1251:仙岛求药

题目相关

题目链接

一本通 OJ,http://ybt.ssoier.cn:8088/problem_show.php?pid=1251

计蒜客 OJ,https://nanti.jisuanke.com/t/T1212

题目描述

少年李逍遥的婶婶病了,王小虎介绍他去一趟仙灵岛,向仙女姐姐要仙丹救婶婶。叛逆但孝顺的李逍遥闯进了仙灵岛,克服了千险万难来到岛的中心,发现仙药摆在了迷阵的深处。迷阵由M×N个方格组成,有的方格内有可以瞬秒李逍遥的怪物,而有的方格内则是安全。现在李逍遥想尽快找到仙药,显然他应避开有怪物的方格,并经过最少的方格,而且那里会有神秘人物等待着他。现在要求你来帮助他实现这个目标。下图,显示了一个迷阵的样例及李逍遥找到仙药的路线。

输入格式

输入有多组测试数据. 每组测试数据以两个非零整数 M 和 N 开始,两者均不大于20。M 表示迷阵行数, N 表示迷阵列数。接下来有 M 行, 每行包含N个字符,不同字符分别代表不同含义:

1)‘@’:少年李逍遥所在的位置;

2)‘.’:可以安全通行的方格;

3)‘#’:有怪物的方格;

4)‘*’:仙药所在位置。

当在一行中读入的是两个零时,表示输入结束。

输出格式

对于每组测试数据,分别输出一行,该行包含李逍遥找到仙药需要穿过的最少的方格数目(计数包括初始位置的方块)。如果他不可能找到仙药, 则输出 -1。

扫描二维码关注公众号,回复: 9575698 查看本文章

样例输入

8 8
.@##...#
#....#.#
#.#.##..
..#.###.
#.#...#.
..###.#.
...#.*..
.#...###

样例输出

10

题目分析

走迷宫问题,自然是典型的 BFS 模板题。

题意分析

一个 M*N 大小的迷宫,我们从 @ 位置出发(也就是起点),字符 . 表示可以安全通行的方格,字符 # 表示有怪物(也就是不能走),字符 * 表示仙药(也就是终点)。要求输出从 @ 到 * 的最短路径。那么迷宫问题的基本要素全齐了,所以本题就是一道 BFS 模板题。

样例数据分析

迷宫大小

w = 8,h=8。

坐标系建立

如下图所示,我们这样建立坐标系。

我比较喜欢从 0 开始,当然也可以从 1 开始建立。

起点坐标

从上图坐标系中,我们可以知道,起点 @ 的坐标为 (1, 0)。

终点坐标

从上图坐标系中,我们可以知道,终点 * 的坐标为 (5, 6)。

移动方式

一般走迷宫问题,缺省的移动方向就是四个,即上下左右。根据上面的坐标系,我们可以定义出如下的移动增量:

const POS move[] = {{-1,0}, {0,1}, {1,0}, {0,-1}};

从哪个方向开始,如何摆放位置,是不会影响最终的结果。

最终的移动路线

如下图绿色所示,最终最短的路径是 10 步。

算法思路

1、读入数据,并写入到合适的数据结构中。

2、找到起点位置,将起点加入到队列 q 中。

3、记录终点位置信息。

4、开始 BFS 遍历。直到找到终点或者遍历所有节点而无法到达终点。

AC 参考代码

#include <cstdio>
#include <queue>

struct POS {
	int x, y;//当前结点坐标
	int cost;//从起点到当前结点的距离
};

const int MAXN = 200;
struct MAZE {
	int m, n;//迷宫大小
	char data[MAXN][MAXN];//迷宫的数据
	bool visit[MAXN][MAXN];//是否已经走过
	int x1, y1;//起点信息
	int x2, y2;//终点信息
};

int bfs(MAZE &maze);

int main() {
	MAZE maze = {};
	scanf("%d %d", &maze.m, &maze.n);

	//读入迷宫
	int i, j;
	for (i=0; i<maze.m; i++) {
		for (j=0; j<maze.n; j++) {
			scanf(" %c", &maze.data[i][j]);
			if (maze.data[i][j]=='@') {
				//起点位置
				maze.x1 = i;
				maze.y1 = j;
			} else if (maze.data[i][j]=='*') {
                                //终点位置
				maze.x2 = i;
				maze.y2 = j;
			}
		}
	}
	
        //使用 BFS 进行遍历
	printf("%d\n", bfs(maze));
	
	return 0;
}

int bfs(MAZE &maze) {
	std::queue<POS> q;
	const POS move[] = {{-1,0}, {0,1}, {1,0}, {0,-1}};//定义移动方法
	POS cur, next;
	
	//加入起点,从起点位置开始遍历
	cur.x = maze.x1;
	cur.y = maze.y1;
	cur.cost = 0;
	maze.visit[cur.x][cur.y] = true;
	q.push(cur); 
	
	//开始遍历
	while (!q.empty()) {
                //弹出队首节点
		cur = q.front();
		q.pop();
		
                //从队首节点出发,尝试所有的移动可能
		for (int i=0; i<4; i++) {
                        //计算下一个节点坐标
			next.x = cur.x + move[i].x;
			next.y = cur.y + move[i].y;
			
			//判断是不是终点
			if (next.x==maze.x2 && next.y==maze.y2) {
				return cur.cost + 1;
			} 
			
			//判断通过性
			if (next.x>=0&&next.x<maze.m&&next.y>=0&&next.y<maze.n&&maze.visit[next.x][next.y]==false&&maze.data[next.x][next.y]!='#') {
                                //说明这个节点可以通过,加入到队列中
				next.cost = cur.cost + 1;
				if (maze.data[next.x][next.y]=='x') {
					next.cost++;
				}
				maze.visit[next.x][next.y] = true;
				q.push(next);
			}
		}
	}
	
	return -1; 
}

代码分析

1、如何表示一个节点的坐标,以及该节点到起点的距离。这里我用一个自定义的结构体来表示。如下所示:


struct POS {
    int x, y;//当前结点坐标
    int cost;//从起点到当前结点的距离
};

2、如何表示一个迷宫。这里我将所有迷宫信息全部放在一个自定义结构体中,增强了代码可读性。如下所示:

const int MAXN = 200;
struct MAZE {
    int m, n;//迷宫大小
    char data[MAXN][MAXN];//迷宫的数据
    bool visit[MAXN][MAXN];//是否已经走过
    int x1, y1;//起点信息
    int x2, y2;//终点信息
};

3、如何表示所有移动可能性。如下所示:

const POS move[] = {{-1,0}, {0,1}, {1,0}, {0,-1}};//定义移动方法

4、新节点通过性判断问题。根据题目进行判断,基本包括以下几个方面:

(1)这个位置可以走,如本题中用字符 . 表示。如下所示:

maze.data[next.x][next.y]!='#'

(2)这个位置没有访问过。如下所示:

maze.visit[next.x][next.y]==false

(3)这个位置处于迷宫内。这个判断和您程序对迷宫定义有关。如下所示:

next.x>=0&&next.x<maze.m&&next.y>=0&&next.y<maze.n

P.S.

本题解不能完全适用于一本通的题目,因为一本通的输入里有多个迷宫。只需要将代码简单改动即可。

发布了204 篇原创文章 · 获赞 103 · 访问量 104万+

猜你喜欢

转载自blog.csdn.net/justidle/article/details/104651311