问题 U: 推箱子游戏-广度优先搜索版本

发布于:2024-07-01 ⋅ 阅读:(19) ⋅ 点赞:(0)

问题 U: 推箱子游戏-广度优先搜索版本

题目描述
推箱子是一款经典游戏。这里我们玩的是一个简单版本,就是在一个N*M的地图上,有1个玩家、1个箱子、1个目的地以及若干障碍,其余是空地。玩家可以往上下左右4个方向移动,但是不能移动出地图或者移动到障碍里去。如果往这个方向移动推到了箱子,箱子也会按这个方向移动一格,当然,箱子也不能被推出地图或推到障碍里。当箱子被推到目的地以后,游戏目标达成。现在告诉你游戏开始是初始的地图布局,请问玩家至少要多少步才能达成目标?
输入
第一行输入两个数字N,M表示地图的大小。其中0<N,M<=12。
接下来有N行,每行包含M个字符表示游戏地图。其中 . 表示空地、X表示玩家、*表示箱子、#表示障碍、@表示目的地。

输出
输出一个数字表示玩家最少需要移动多少步才能将游戏目标达成。当无论如何达成不了的时候,输出-1。

样例输入 Copy
6 6
...#..
......
#*##..
..##.#
..X...
.@#...
样例输出 Copy
11

实现过程

整体分析:

  1. 问题定义:
    推箱子游戏是一个经典的谜题,其中玩家需要在一个网格中移动箱子到指定位置。使用BFS算法可以帮助找到从初始状态到目标状态的最短路径。
  2. 算法选择:
    BFS是一种适合于寻找最短路径的搜索算法,它从起点开始,逐层遍历节点,直到找到目标节点。在推箱子游戏中,BFS可以帮助找到最少步骤的解决方案。
  3. 数据结构设计:
  • 二维数组或矩阵:用于表示游戏的网格,其中包含箱子、障碍物和玩家的位置。
  • 队列:BFS的核心数据结构,用于存储待处理的状态。
  • 状态结构体:包含玩家和箱子的位置信息,以及到达该状态所需的步数。
  1. 输入与输出处理:
  • 输入包括游戏网格的布局和目标状态。
  • 输出为从初始状态到目标状态的一系列移动步骤,或者是无法到达目标状态的信息。
  1. BFS实现步骤:
  • 初始化队列,将起始状态加入队列。
  • 循环直到队列为空:
    • 从队列中取出当前状态。
    • 检查当前状态是否为目标状态,如果是,则输出解决方案并结束搜索。
    • 否则,生成所有可能的合法移动,并将其加入队列。
  1. 状态合法性检查:
  • 确保每次移动后玩家和箱子都在网格内,并且没有箱子被推入障碍物或墙壁。
  1. 避免重复状态:
  • 使用一个集合或哈希表记录已经访问过的状态,避免重复处理。
  1. 边界条件处理:
  • 检查玩家和箱子的移动是否超出了网格边界。
  1. 代码实现:
  • 使用C语言编写BFS算法的逻辑。
  • 实现状态的初始化、入队、出队和状态转换逻辑。
  • 实现搜索结束条件和解决方案的输出。
  1. 测试与验证:
  • 编写测试用例,包括不同的网格布局和目标状态。
  • 验证算法是否能够找到正确的解决方案,并且是最少步骤的解决方案。
  1. 性能优化:
  • 分析算法的时间复杂度和空间复杂度。
  • 考虑使用启发式信息或其他优化技术来减少搜索空间。

使用C语言编写使用广度优先搜索(BFS)实现推箱子游戏的代码,其主要目的包括:

  1. 算法实践:通过实现BFS算法来解决推箱子游戏中的路径搜索问题,加深对算法原理和逻辑流程的理解。
  2. 问题解决技巧:学习如何将实际问题抽象为可计算的问题,并使用计算机算法来寻找解决方案。
  3. 数据结构应用:掌握队列等数据结构在实现BFS中的关键作用,以及如何用其来存储和处理游戏中的状态。
  4. 搜索策略优化:探索和实现不同的搜索策略,比如如何利用启发式信息优化搜索过程,尽管在纯粹的BFS中不使用启发式。
  5. 游戏AI开发:为推箱子游戏开发一个有效的AI,使其能够自动找到从初始状态到目标状态的路径。
  6. 递归与迭代:展示如何使用迭代方法来避免递归可能导致的栈溢出问题,特别是在处理深度较大的搜索时。
  7. 程序设计能力:提升程序设计能力,包括如何组织代码结构、如何管理内存以及如何编写可读性强的代码。
  8. 复杂性管理:学习如何在面对复杂问题时,通过分解问题和模块化设计来简化解决方案。
  9. 测试与验证:通过编写测试用例来验证算法的正确性,确保算法在各种情况下都能正确运行。
  10. 创新思维:鼓励创新思维,探索BFS在解决推箱子问题时的可能性和局限性,以及如何改进算法以适应更复杂的情况。

这段C++代码实现了一个基于广度优先搜索(BFS)算法的迷宫问题求解器,特别适用于解决类似于推箱子这样的谜题。下面是对代码的详细解析:

  1. 头文件和命名空间

    • 包含 <iostream><queue><string> 头文件,分别用于输入输出、队列操作和字符串操作。
    • 使用 using namespace std; 来避免在标准库类型和函数前加 std::
  2. 状态四维数组 state

    • 用于记录人和箱子在不同位置状态的访问情况,初始值全为0。
  3. 结构体 q

    • 用于存储当前状态,包含人的位置 px, py 和箱子的位置 bx, by
  4. 移动方向数组 moves

    • 存储四个可能的移动方向,上、下、左、右。
  5. 地图数组 map

    • 存储迷宫的布局,使用字符数组表示,其中 ‘#“表示墙壁,”*’ 表示箱子,‘X’ 表示人,‘@’ 表示终点。
  6. 边界检查函数 bound

    • 检查给定的位置是否越界或是否为墙壁,如果是,则返回 true
  7. 广度优先搜索算法 bfs

    • 使用一个队列来存储待处理的状态。
    • 从初始状态开始,不断探索所有可能的移动,直到找到目标状态或所有状态都被访问过。
    • 如果找到目标状态,返回到达该状态的步数减1;如果无法找到,则返回-1。
  8. 主函数 main

    • 读取地图尺寸 nm
    • 读取地图布局,同时初始化人、箱子和终点的位置。
    • 调用 bfs 函数进行搜索,并将结果输出。
  9. 程序结束

    • 输出搜索结果后,使用 system("pause") 暂停程序,等待用户操作。

代码逻辑分析

  • 这段代码使用了一个四维数组来存储状态,每个维度分别对应人和箱子在迷宫中的行和列位置。
  • 使用广度优先搜索算法,从初始状态开始,探索所有可能的移动,直到找到目标状态或所有可能的状态都被探索过。

潜在问题

  • 使用 scanf 读取字符串时,没有指定字符串的最大长度,可能导致缓冲区溢出。

改进建议

  • 使用 std::cinstd::getline 替代 scanf 来读取地图布局,以提高代码的安全性和可读性。
  • 考虑使用 std::vectorstd::array 替代原始数组,以提高代码的灵活性和健壮性。
  • 可以添加对输入数据有效性的检查,确保读取的是有效的地图布局。

BFS算法讲解

**广度优先搜索(Breadth-First Search, BFS)**是一种遍历或搜索树或图的算法,它从一个节点开始,逐层遍历节点,即首先访问起始节点的所有邻接节点,然后访问邻接节点的邻接节点,依此类推。这种搜索方法常用于寻找最短路径或仅仅是对图的遍历。
使用一个队列来存储待处理的状态。
从初始状态开始,不断探索所有可能的移动,直到找到目标状态或所有状态都被访问过。
如果找到目标状态,返回到达该状态的步数减1;如果无法找到,则返回-1。

基本概念:
  • 队列:BFS使用队列作为主要的数据结构来保持待访问的节点。
  • 层次遍历:BFS按照节点的层次顺序进行访问,这使得它在寻找最短路径时非常有用。
  • 已访问集合:为了跟踪已经访问过的节点,避免重复访问,通常使用一个集合或哈希表。
算法步骤:
  1. 初始化:将起始节点放入队列,并标记为已访问。
  2. 循环:当队列非空时,执行以下操作:
    a. 从队列中取出一个节点。
    b. 访问该节点,这可能包括检查是否到达目标节点,或者处理节点数据。
    c. 将所有未访问的邻接节点添加到队列中,并标记为已访问。
  3. 终止:当队列为空或找到目标节点时,搜索结束。
应用场景:
  • 最短路径:BFS常用于在未加权图中找到两点间的最短路径。
  • 社交网络:在社交网络分析中,BFS可以用来识别个体的影响力范围。
  • 游戏AI:在游戏开发中,BFS可以用于AI的路径搜索和决策制定。
注意事项:
  • 确保使用合适的数据结构来存储图的信息,邻接矩阵或邻接表都是可行的选择。
  • 在使用队列和已访问集合时,注意内存管理,避免内存泄漏。
  • 对于大型图,考虑使用更高效的数据结构或优化算法以减少时间和空间复杂度。

BFS是一种简单但强大的算法,适用于多种应用场景,特别是在需要层次遍历或寻找最短路径时。

特殊位置解答

在C++中,q(int x, int y, int bx, int by) :px(x), py(y), bx(bx), by(by) {} 是一个结构体 q 的构造函数初始化列表。

解析:

  1. 构造函数q 是结构体名,q(int x, int y, int bx, int by) 定义了一个构造函数,它接收四个参数:xybxby

  2. 参数意义

    • xy 通常表示人(或主角)在网格地图上的坐标。
    • bxby 表示箱子在网格地图上的坐标。
  3. 初始化列表:px(x), py(y), bx(bx), by(by) 是一个初始化列表,它用于在创建 q 类型对象时直接初始化其成员变量。

    • pxpy 是结构体 q 的成员变量,分别存储人的横纵坐标。
    • bxby 是结构体 q 的成员变量,分别存储箱子的横纵坐标。
  4. 作用:这个构造函数的作用是创建一个 q 类型的对象,并用传入的参数值初始化该对象的成员变量。这样做的好处是可以直接在创建对象时设置其状态,避免了后续再单独设置成员变量的需要。

  5. 使用场景:在这段代码中,构造函数用于初始化表示搜索状态的对象。在广度优先搜索(BFS)算法中,每个状态(人和箱子的位置)都需要被明确记录和处理。使用构造函数可以方便地将这些状态封装为对象。

  6. 代码示例

    struct q {
        int px, py, bx, by;
        q(int x, int y, int bx, int by) :px(x), py(y), bx(bx), by(by) {} // 构造函数
    };
    

    在 BFS 中使用:

    q temp(chx, chy, cbx, cby); // 创建一个状态对象,初始为人在 (chx, chy),箱子在 (cbx, cby)
    

q(int x, int y, int bx, int by) :px(x), py(y), bx(bx), by(by) {} 是一个构造函数,通过初始化列表设置结构体 q 的成员变量,使得每个 q 类型的对象都能清晰地表示一个搜索状态。在算法实现中,这种封装和初始化方式有助于代码的清晰性和易管理性。

部分实现

边界检查,遇到不合理的位置返回真

bool bound(int x, int y)//边界检查,遇到不合理的位置返回真  
{
	if (x < 0 || y < 0 || x >= n || y >= m || map[x][y] == '#')
		return true;
	else
		return false;
}

广度优先算法


int bfs()
{
	state[chx][chy][cbx][cby] = 1;//当前其实状态位置设步数为1  
	q temp(chx, chy, cbx, cby);
	queue<q> que; //状态队列  
	que.push(temp);//初始状态入栈  
	while (que.size()) //只要队列不为空就一直寻找  
	{
		temp = que.front();//获取首元素  
		que.pop();//首元素弹出  
		if (temp.bx == ex&&temp.by == ey)
			return state[temp.px][temp.py][temp.bx][temp.by] - 1;//如果箱子在终点,结束,返回步数  
		for (int i = 0; i < 4; i++)//四个方向开始搜索了  
		{
			//先更新人的位置  
			int px = temp.px + moves[i][0];
			int py = temp.py + moves[i][1];
			if (bound(px, py))
				continue;//如果这个位置非法,探寻其它方向  
			if (px == temp.bx&&py == temp.by)//如果此时人的位置与箱子的位置重合,说明人应当推动了箱子  
			{
				if (bound(temp.bx + moves[i][0], temp.by + moves[i][1]))
					continue;//如果箱子移动的位置不合法,则重新探寻其它方向  
				state[px][py][temp.bx + moves[i][0]][temp.by + moves[i][1]] =
					state[temp.px][temp.py][temp.bx][temp.by] + 1;//箱子推动,则人和箱子位置改变,记录新状态  
				que.push(q(px, py, temp.bx + moves[i][0], temp.by + moves[i][1]));//新状态入栈  
 
			}
			else//人没有推动箱子  
			{
				if (state[px][py][temp.bx][temp.by])//如果移动后的状态出现过,则重新搜索新方向  
					continue;
				state[px][py][temp.bx][temp.by] = state[temp.px][temp.py][temp.bx][temp.by] + 1;
				//没有走过这条路就走着试试  
				que.push(q(px, py, temp.bx, temp.by));//更新状态  
 
 
			}
 
		}
	}
	return -1;//如果所有位置都试过了,没有找到,说明不存在  
 
}

AC代码

#include<iostream>  
#include<queue>  
#include<string>  
using namespace std;
int state[10][10][10][10];//四维数组表示人和箱子的位置状态,开始全为0  
 
struct q
{
	int px, py, bx, by;
	q(int x, int y, int bx, int by) :px(x), py(y), bx(bx), by(by) {}
};
int moves[4][2] = { { 0,1 },{ 0,-1 },{ -1,0 },{ 1,0 } };//四个方向  
char map[10][10];//地图数组  
int chx, chy, cbx, cby, ex, ey, n, m;//分别表示当前人的位置,盒子的位置,终点位置,以及地图尺寸。  
 
bool bound(int x, int y)//边界检查,遇到不合理的位置返回真  
{
	if (x < 0 || y < 0 || x >= n || y >= m || map[x][y] == '#')
		return true;
	else
		return false;
}
//广度优先算法
int bfs()
{
	state[chx][chy][cbx][cby] = 1;//当前其实状态位置设步数为1  
	q temp(chx, chy, cbx, cby);
	queue<q> que; //状态队列  
	que.push(temp);//初始状态入栈  
	while (que.size()) //只要队列不为空就一直寻找  
	{
		temp = que.front();//获取首元素  
		que.pop();//首元素弹出  
		if (temp.bx == ex&&temp.by == ey)
			return state[temp.px][temp.py][temp.bx][temp.by] - 1;//如果箱子在终点,结束,返回步数  
		for (int i = 0; i < 4; i++)//四个方向开始搜索了  
		{
			//先更新人的位置  
			int px = temp.px + moves[i][0];
			int py = temp.py + moves[i][1];
			if (bound(px, py))
				continue;//如果这个位置非法,探寻其它方向  
			if (px == temp.bx&&py == temp.by)//如果此时人的位置与箱子的位置重合,说明人应当推动了箱子  
			{
				if (bound(temp.bx + moves[i][0], temp.by + moves[i][1]))
					continue;//如果箱子移动的位置不合法,则重新探寻其它方向  
				state[px][py][temp.bx + moves[i][0]][temp.by + moves[i][1]] =
					state[temp.px][temp.py][temp.bx][temp.by] + 1;//箱子推动,则人和箱子位置改变,记录新状态  
				que.push(q(px, py, temp.bx + moves[i][0], temp.by + moves[i][1]));//新状态入栈  
 
			}
			else//人没有推动箱子  
			{
				if (state[px][py][temp.bx][temp.by])//如果移动后的状态出现过,则重新搜索新方向  
					continue;
				state[px][py][temp.bx][temp.by] = state[temp.px][temp.py][temp.bx][temp.by] + 1;
				//没有走过这条路就走着试试  
				que.push(q(px, py, temp.bx, temp.by));//更新状态  
 
 
			}
 
		}
	}
	return -1;//如果所有位置都试过了,没有找到,说明不存在  
 
}
 
 
int main()
{
	cin >> n >> m;

	for (int i = 0; i < n; i++) {
		scanf("%s", map[i], m + 1);
	}
	for (int i = 0; i < n; i++)//初始化人,箱子,终点的位置  
	{
		for (int j = 0; j < m; j++)
		{
			if (map[i][j] == '*')
			{
				cbx = i;
				cby = j;
			}
			else if (map[i][j] == 'X') {
				chx = i;
				chy = j;
			}
			else if (map[i][j] == '@')
			{
				ex = i;
				ey = j;
			}
		}
	}
	cout << bfs() << endl;
	system("pause");
	return 0;
}

网站公告

今日签到

点亮在社区的每一天
去签到