BFS广度优先遍历搜索最短路径(迷宫问题)

最近笔试经常遇到路径搜索的问题,可是每一次都不知道如何去写,决定整理一下。
其实都是用bfs解决的。最简单的迷宫类问题就是输入一个m*n的矩阵,其中0代表可走的路,1代表障碍物,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。

先来看第一题

题目描述:
假设以一个nm的矩阵作为棋盘,每个棋位对应一个二维坐标 (x, y)。你有一颗棋子位于左上起点(0, 0),现在需要将其移动到右下底角 (n-1, m-1),棋子可以向相邻的上下左右位置移动,每个坐标最多只能经过一次。棋盘中散布着若干障碍,障碍物不能跨越,只能绕行,问是否存在到达右下底角的路线?若存在路线,输出所需的最少移动次数;若不存在,输出0。 Input 第一行三个正整数n,m和k,代表棋盘大小与障碍物个数 1< n、m < 100, k < nm 第二行至第k+1行,每行为两个整数x和y,代表k个障碍物的坐标。
输入
输入三个正整数n,m和k,代表棋盘大小与障碍物个数 1< n、m < 100, k < n*m
第二行至第k+1行,每行为两个整数x和y,代表k个障碍物的坐标。
输出
输出从起点到终点的最短路径的长度,如果不存在,即输出0
样例输入
5 10 20
1 4
0 3
2 2
4 4
1 7
1 3
2 3
1 8
3 7
3 5
1 5
3 9
4 8
4 0
4 1
2 1
0 7
2 4
4 5
0 8
样例输出
0
思路
这道题没有直接让输矩阵,而是告诉了障碍物的下标,那么首先我们要转换为最基本的问题,就是把有障碍物的位置用1表示,其余用0表示。
代码

#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>

using namespace std;

int ab[4][2] = { { 0,1},{0,-1},{1,0},{-1,0 } };
struct node {
	int hx, hy, step;
};
struct father
{
	int x;
	int y;
};

queue<node> q;//队列
node start;//入口
node endd;//出口
node rec[20][20];//记录最短路径

int bfs(vector<vector<int>> maze, int m, int n) {
	vector<int> v(n);
	vector<vector<int>> vis(m, v);//标记每一点是否被访问过	

	//设置起点和终点的坐标
	start.hx = 0; start.hy = 0; start.step = 0;
	endd.hx = m - 1; endd.hy = n - 1;
	q.push(start);
	vis[start.hx][start.hy] = 1;
	while (!q.empty())
	{
		node s = q.front();
		q.pop();
		for (int i = 0; i < 4; i++) {
			node tmp = s;
			tmp.step++;
			tmp.hx += ab[i][0];
			tmp.hy += ab[i][1];

			if (tmp.hx > n - 1 || tmp.hy > m - 1 || tmp.hx < 0 || tmp.hy < 0) continue;

			if (maze[tmp.hx][tmp.hy] == 1) continue;

			if (vis[tmp.hx][tmp.hy] != 1)
			{
				q.push(tmp);
				vis[tmp.hx][tmp.hy] = 1;
				rec[tmp.hx][tmp.hy].hx = s.hx;
				rec[tmp.hx][tmp.hy].hy = s.hy;
			}
			if (tmp.hx == endd.hx&&tmp.hy == endd.hy)
			{
				return tmp.step;
			}
				
		}
	}
	return -1;
}

void dfs(int x, int y)
{

	if (x == 0 && y == 0) return;//找到父节点是起点的格子了
	else
		dfs(rec[x][y].hx, rec[x][y].hy);
	cout << rec[x][y].hx << " " << rec[x][y].hy << endl;

}

int main()
{
	int k;//障碍物个数
	int m, n;//迷宫的行和列
	cin >> n >> m >> k;
	vector<int> ma(n);
	vector<vector<int>> maze(m, ma);//迷宫
	
	vector<int> obp(2);
	vector<vector<int>> ob(k,obp);
	for (int i = 0; i < k; i++)
	{
		for (int j = 0; j < 2; j++)
		{
			cin >> ob[i][j];
		}
	}

	for(int i=0;i<k;i++)
	{			
		maze[ob[i][0]][ob[i][1]] = 1;
	}
	/*for (int i = 0; i < m; i++)
	{
		for (int j = 0; j < n; j++)
		{
			cout << maze[i][j]<<" ";//输出构造好的迷宫
		}		
		cout << endl;
	}*/
	int ans = bfs(maze, m, n);
	if (ans < 0) 
		cout << 0 << endl;
	else
	{
		cout << ans << endl;
		dfs(n-1, m-1);//从终点开始找他的父节点
	}
	cout << m-1 << " " << n-1 << endl;

 	system("pause");
	return 0;
}

第二题

题目描述
薯队长最近在玩一个迷宫探索类游戏,迷宫是一个N*N的矩阵形状,其中会有一些障碍物禁止通过。这个迷宫还有一个特殊的设计,它的左右边界以及上下边界是连通的,比如在(2,n)的位置继续往右走一格可以到(2,1), 在(1,2)的位置继续往上走一格可以到(n,2)。请问薯队长从起点位置S,最少走多少格才能到达迷宫的出口位置E。
输入
第一行正整数 N。 接下来 N 行 字符串。
’.’表示可以通过,’#’表示障碍物, ’S’表示起点(有且仅有一个),’E’表示出口(有且仅有一个)。
对于50%的数据
0 < N < 10
对于100%的数据
0 < N < 10^3
输出
输出一个整数。表示从S到E最短路径的长度, 无法到达则输出 -1
样例输入
5
.#…
…#S.
.E###
……
……
样例输出
4
提示
向右来到(2,5)
向右来到(2,1)
向下来到(3,1)
向右来到(3,2)
思路
和第一道题不一样的是起点和终点不再是迷宫的左上角和右下角了。很简单,遍历迷宫,找到起点和终点坐标就好了。其实这道题有一个难点就是迷宫的它的左右边界以及上下边界是连通的。暂时没时间想,就先放一个不连通的代码吧。
代码

#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <queue>

using namespace std;

int ab[4][2] = { { 0,1},{0,-1},{1,0},{-1,0 } };
struct node {
	int hx, hy, step;
};
struct father
{
	int x;
	int y;
};

queue<node> q;//队列
node start;//入口
node endd;//出口
int n;
vector<string> maze;//迷宫
node rec[20][20];//记录最短路径

int bfs() {
	vector<int> v(n);
	vector<vector<int>> vis(n, v);//标记每一点是否被访问过

	q.push(start);
	vis[start.hx][start.hy] = 1;
	while (!q.empty())
	{
		node s = q.front();
		q.pop();
		for (int i = 0; i < 4; i++) {
			node tmp = s;
			tmp.step++;
			tmp.hx += ab[i][0];
			tmp.hy += ab[i][1];

			if (tmp.hx > n - 1 || tmp.hy > n - 1 || tmp.hx < 0 || tmp.hy < 0) continue;//超出边界

			if (maze[tmp.hx][tmp.hy] == '#') continue;//遇到障碍物

			if (vis[tmp.hx][tmp.hy] != 1)
			{
				q.push(tmp);
				vis[tmp.hx][tmp.hy] = 1;
				rec[tmp.hx][tmp.hy].hx = s.hx;
				rec[tmp.hx][tmp.hy].hy = s.hy;
			}
			if (tmp.hx == endd.hx&&tmp.hy == endd.hy)
			{
				return tmp.step;
			}

		}
	}
	return -1;
}

void dfs(int x, int y)
{
	if (x == start.hx && y == start.hy) return;//找到父节点是起点的格子了
	else
		dfs(rec[x][y].hx, rec[x][y].hy);
	cout << rec[x][y].hx << " " << rec[x][y].hy << endl;
}

int main() {
	
	cin >> n;
	
	for (int i = 0; i < n; i++)
	{
		string str;
		cin >> str;
		maze.push_back(str);
	}

	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
		{
			if (maze[i][j] == 'S')
				start.hx = i, start.hy = j;//将开始点存储到start中
			if (maze[i][j] == 'E')
				endd.hx = i, endd.hy = j;//将终点存储到endd中
		}
	}
	int res = bfs();
	cout << res << endl;
	if (res > 0)
	{
		dfs(endd.hx, endd.hy);
	}
	system("pause");
	return 0;
}

推箱子

题目描述
大家一定玩过“推箱子”这个经典的游戏。具体规则就是在一个NM的地图上,有1个玩家、1个箱子、1个目的地以及若干障碍,其余是空地。玩家可以往上下左右4个方向移动,但是不能移动出地图或者移动到障碍里去。如果往这个方向移动推到了箱子,箱子也会按这个方向移动一格,当然,箱子也不能被推出地图或推到障碍里。当箱子被推到目的地以后,游戏目标达成。现在告诉你游戏开始是初始的地图布局,请你求出玩家最少需要移动多少步才能够将游戏目标达成。
输入描述:
每个测试输入包含1个测试用例
第一行输入两个数字N,M表示地图的大小。其中0<N,M<=8。
接下来有N行,每行包含M个字符表示该行地图。其中 . 表示空地、X表示玩家、表示箱子、#表示障碍、@表示目的地。
每个地图必定包含1个玩家、1个箱子、1个目的地。
输出描述:
输出一个数字表示玩家最少需要移动多少步才能将游戏目标达成。当无论如何达成不了的时候,输出-1。
输入例子1:
4 4

@

.X…
6 6
…#…

#
##…
…##.#
…X…
.@#…
输出例子1:
3
11
思路

#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <queue>

using namespace std;

int ab[4][2] = { { 0,1},{0,-1},{1,0},{-1,0 } };//右左下上

struct node {
	int hx, hy, step;
	int bx, by;
};
queue<node> q;//队列
node start;//起点
int m, n;
vector<vector<char>> maze;//迷宫
int vis[8][8][8][8];

int bfs() {

	q.push(start);
	while (!q.empty())
	{
		node s = q.front();
		q.pop();
		vis[s.hx][s.hy][s.bx][s.by] = 1;
		if (maze[s.bx][s.by] == '@') return s.step;//箱子成功推到目的地
		for (int i = 0; i < 4; i++) {
			node tmp = s;
			tmp.step++;
			tmp.hx += ab[i][0];
			tmp.hy += ab[i][1];

			if (tmp.hx > n - 1 || tmp.hy > m - 1 || tmp.hx < 0 || tmp.hy < 0) continue;//人物越界

			if (maze[tmp.hx][tmp.hy] == '#') continue;//人碰到障碍物

			if (tmp.hx == tmp.bx && tmp.hy == tmp.by)
				tmp.bx += ab[i][0], tmp.by += ab[i][1];

			if (tmp.bx > n - 1 || tmp.by > m - 1 || tmp.bx < 0 || tmp.by < 0) continue;//箱子越界

			if (maze[tmp.bx][tmp.by] == '#') continue;//箱子碰到障碍物

			if (vis[tmp.hx][tmp.hy][tmp.bx][tmp.by] != 1)
			{
				q.push(tmp);
			}
		}
	}
	return -1;
}

int main() {
	cin >> n >> m;
	
	for (int i = 0; i < n; i++)
	{
		vector<char> str;
		for (int j = 0; j < m; j++)
		{
			char ch;
			cin >> ch;			
			str.push_back(ch);
		}
		maze.push_back(str);
	}

	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < m; j++)
		{
			if (maze[i][j] == 'X')
				start.hx = i, start.hy = j;//记录玩家的起点位置

			if (maze[i][j] == '*')
				start.bx = i, start.by = j;//记录box的位置
		}
	}

	int res = bfs();
	cout << res << endl;
	
	system("pause");
	return 0;
}
### 回答1: 广度优先遍历可以用来寻找迷宫中的路径。下面是一个示例代码: ```python from queue import Queue def bfs(maze, start, end): queue = Queue() visited = set() queue.put(start) visited.add(start) path = {} path[start] = None while not queue.empty(): current = queue.get() if current == end: break for neighbor in get_neighbors(maze, current): if neighbor not in visited: queue.put(neighbor) visited.add(neighbor) path[neighbor] = current return get_path(path, start, end) def get_neighbors(maze, cell): neighbors = [] rows, cols = len(maze), len(maze[0]) row, col = cell if row > 0 and maze[row-1][col] == 0: neighbors.append((row-1, col)) if row < rows-1 and maze[row+1][col] == 0: neighbors.append((row+1, col)) if col > 0 and maze[row][col-1] == 0: neighbors.append((row, col-1)) if col < cols-1 and maze[row][col+1] == 0: neighbors.append((row, col+1)) return neighbors def get_path(path, start, end): current = end result = [] while current != start: result.append(current) current = path[current] result.append(start) result.reverse() return result ``` 其中,`maze` 是一个二维数组,表示迷宫,其中 0 表示可通过的格子,1 表示障碍物。`start` 和 `end` 分别表示起点和终点。 函数首先创建一个队列 `queue` 和一个集合 `visited`,将起点加入队列和集合中。然后创建一个字典 `path`,用于记录每个格子的前驱格子。接着进入循环,直到队列为空或者找到终点为止。每次从队列中取出一个格子 `current`,并遍历它的相邻格子,如果相邻格子是可通过的且没有被访问过,则将其加入队列和集合中,并在 `path` 中记录它的前驱格子为 `current`。 最后,调用 `get_path` 函数获取从起点到终点的路径。`get_path` 函数从终点开始,沿着 `path` 中记录的前驱格子一路向前,直到到达起点为止。 ### 回答2: 广度优先遍历是一种图的遍历算法,用于寻找迷宫中的路径。在迷宫中,我们可以将每个格子看作一个节点,相邻的格子之间存在着边。迷宫中通畅的路径就是从起始节点到目标节点的一条有效路径。 广度优先遍历算法的基本思想是从起始节点开始,依次访问其所有相邻的节点,并将这些节点依次加入一个队列中。然后再从队列头取出下一个节点进行访问,直到找到目标节点或者队列为空。 具体实现迷宫路径的广度优先遍历可以按照以下步骤进行: 1. 创建一个队列,将起始节点加入队列中。 2. 创建一个visited集合,用于记录已经访问过的节点。 3. 创建一个prev字典,用于记录每个节点的前驱节点,即每个节点是从哪个节点遍历而来。 4. 对于队列不为空的情况,重复以下步骤: - 从队列头取出一个节点作为当前节点。 - 如果当前节点是目标节点,则停止遍历,此时可以通过prev字典生成迷宫的路径。 - 否则,将当前节点标记为已访问,并遍历其所有相邻的节点: - 如果相邻节点没有被访问过,则将其加入队列,并将当前节点设置为其前驱节点。 5. 如果队列为空,但仍未找到目标节点,则说明迷宫中不存在可到达目标节点的路径。 通过广度优先遍历,我们可以找到从起始节点到目标节点的最短路径,因为遍历过程中,第一次访问到目标节点时,一定是经过的路径最短的。使用prev字典即可回溯出路径。 ### 回答3: 广度优先遍历是一种图的搜索算法,可以用于解决迷宫路径的问题。迷宫可以看作是一个由行和列组成的二维矩阵,其中的每个元素可以是墙壁、通路或终点。 广度优先遍历利用队列来实现,从起点开始,将起点入队,并将其标记为已访问。然后,不断从队列中取出元素,并检查其周围的相邻节点是否可访问。如果可访问,则将其入队,并标记为已访问。直到队列为空为止,即找到了终点或者无法找到路径。 具体实现时,可以使用一个二维数组来表示迷宫,其中的元素值表示该位置的状态(0表示墙壁,1表示通路,2表示终点)。另外,可以使用一个二维数组来记录每个位置的步数,初始值为0。 首先,将起点入队,并将起点的步数设置为1。然后,进入循环,直到队列为空。在循环中,从队列中取出一个节点,再枚举其四个相邻节点(上、下、左、)。对于每个相邻节点,如果该节点可访问且未被访问过,则将其入队,并将其步数设置为当前节点的步数加1。同时,标记该位置为已访问。如果某个相邻节点的值为2,则说明找到了终点,此时可退出循环。 最后,可以通过回溯的方式,从终点开始,沿着步数递减的路径依次找到起点,并将路径上的位置存入一个栈中。最后,将栈中的元素依次出栈,即可得到起点到终点的路径。 总结起来,广度优先遍历实现迷宫路径的步骤如下: 1. 将起点入队,并将其标记为已访问。 2. 进入循环,直到队列为空。 - 从队列中取出一个节点。 - 枚举其四个相邻节点。 - 如果相邻节点可访问且未被访问过,将其入队,并将其步数设置为当前节点的步数加1。同时,标记该位置为已访问。 - 如果某个相邻节点的值为2,退出循环。 3. 通过回溯的方式,找到起点到终点的路径,并存入一个栈中。 4. 将栈中的元素依次出栈,得到起点到终点的路径。 通过以上步骤,就可以使用广度优先遍历找到迷宫的路径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值