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

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

题目描述
推箱子是一款经典游戏。这里我们玩的是一个简单版本,就是在一个N*M的地图上,有1个玩家、1个箱子、1个目的地以及若干障碍,其余是空地。玩家可以往上下左右4个方向移动,但是不能移动出地图或者移动到障碍里去。如果往这个方向移动推到了箱子,箱子也会按这个方向移动一格,当然,箱子也不能被推出地图或推到障碍里。当箱子被推到目的地以后,游戏目标达成。现在告诉你游戏开始是初始的地图布局,要求用深度优先搜索找到游戏的解(注意这里不保证步数最少)。

玩家每到一个格子,就按上(U),右®,下(D),左(L)顺时针方向尝试,每一个方向都都在前一个方向失败时才可能尝试。如下图,如果s6为终态,则游戏解为UU; 如果s21为终态,则玩家要尝试UU,UR,UD,UL,RU,RR,RD,RL,…,LD, 才能确定LL是游戏的解。

在这里插入图片描述

状态由玩家位置和箱子位置构成,算法结构大体如下:
DFS(state s){
for(i = 0; i < 4; i++)
DFS(trans(s, i)); //tans表示状态s往方向i走形成的新状态
}
注意得到解后要立即返回。
输入
第一行输入两个数字N,M表示地图的大小。其中0<N,M<=12。
接下来有N行,每行包含M个字符表示游戏地图。其中 . 表示空地、X表示玩家、*表示箱子、#表示障碍、@表示目的地。
输出
有解时,输出玩家走的每一步。当无论如何达成不了的时候,输出-1。

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

提示
为了帮助大家理解,下面再给出两组数据
3 3
X..
#*.
@..
对应的DFS
RDURDDL

 

3 3
X.@
.*.
...
对应的DFS
RRDDLURDLLUUR

实现过程

使用C语言编写使用深度优先搜索(DFS)实现推箱子游戏的代码是一项综合性的编程任务,它涉及到递归、搜索算法和游戏逻辑。以下是对该项目的整体分析和目的的讨论:

整体分析:

  1. 问题理解
    推箱子游戏是一个典型的搜索问题,目标是在有限的空间内通过推动箱子到达指定位置。

  2. 算法选择
    深度优先搜索适合于解决这类问题,因为它能够深入探索每一种可能的移动,直到找到解决方案或确定无解。

  3. 数据结构

    • 使用二维数组表示游戏地图,包括玩家、箱子、空地和障碍物。
    • 使用结构体或类来表示游戏状态,可能包括玩家位置、箱子位置和目标状态。
  4. 递归实现
    DFS通过递归调用来实现,每次递归尝试一种移动,并回溯以探索其他可能性。

  5. 搜索策略

    • 定义搜索的深度,即玩家可以移动的最大步数。
    • 确保在搜索过程中不重复访问同一状态。
  6. 状态管理
    使用栈数据结构来存储递归调用的状态,包括当前位置和移动历史。

  7. 边界和规则检查
    在每次移动后,检查是否超出边界、是否移动到障碍物上或是否已经达到目标状态。

  8. 回溯机制
    当当前路径无法到达目标时,使用回溯返回到上一个决策点,并尝试其他移动。

  9. 代码实现

    • 实现递归函数来执行搜索。
    • 实现辅助函数来处理移动、状态复制和目标检测。
  10. 测试和验证
    编写测试用例来验证算法的正确性和有效性。

目的:

  1. 算法学习
    通过实现DFS,深入理解其工作原理和适用场景。

  2. 问题解决能力
    提升解决复杂问题的能力,学习如何将问题分解为可管理的子问题。

  3. 编程技能
    增强C语言编程技能,特别是在递归、指针和数据结构方面的应用。

  4. 游戏AI开发
    为推箱子游戏开发AI,提供自动化解决方案,增加游戏的互动性和趣味性。

  5. 搜索算法应用
    探索搜索算法在游戏开发和其他领域的应用,如路径规划、决策制定等。

  6. 逻辑思维和创新
    锻炼逻辑思维,鼓励创新性地思考问题和解决方案。

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

  1. 头文件和命名空间

    • 包含 <iostream><stdlib.h><string.h><stdio.h> 头文件。
    • 使用 using namespace std; 来避免在标准库类型和函数前加 std::
  2. 全局变量

    • zmap:二维字符数组,表示迷宫地图。
    • jmap:四维整型数组,用于标记是否已经访问过某个状态。
    • n1, m1:表示终点的位置。
    • n2, m2:表示起点(人)的位置。
    • n3, m3:表示箱子的位置。
    • n, m:表示地图的尺寸。
    • people:表示人可以移动的四个方向。
    • road:记录人走的路径。
    • k:记录路径长度。
    • vroad:与 people 方向对应,记录移动方向的字符。
    • liu:记录到达终点后的返回值。
  3. 边界检查函数 OutMap

    • 检查给定的位置是否越界或是否为墙壁('#'),如果是,则返回 true
  4. 深度优先搜索算法 venture

    • 递归函数,用于探索从当前状态到终点的所有可能路径。
    • 如果到达终点,返回 1 并记录路径;如果无法到达终点,返回 0
  5. 主函数 main

    • 读取地图尺寸 nm
    • 读取地图布局,同时初始化人、箱子和终点的位置。
    • 调用 venture 函数进行搜索。
    • 如果 k0,表示无法到达终点,输出 -1;否则,逆序输出路径。
  6. 程序结束

    • 输出搜索结果后,程序结束。

代码逻辑分析

  • 这段代码使用深度优先搜索算法来寻找从起点到终点的路径,同时需要推动箱子到达指定位置。
  • 使用一个四维数组 jmap 来记录访问过的状态,避免重复访问。
  • 使用一个字符串 road 来记录从起点到终点的路径。

潜在问题

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

改进建议

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

DFS算法讲解

**深度优先搜索(Depth-First Search, DFS)**是一种用于遍历或搜索树或图的算法。它从一个节点开始,沿着树的深度遍历节点,尽可能深地搜索树的分支。当节点v的子节点都已被访问过,搜索将回溯到节点v的父节点,继续搜索其余的子节点。

核心概念:
  • 递归:DFS通常使用递归实现,每次递归处理一个节点的所有邻接节点。
  • :在非递归实现中,使用栈来保存待访问的节点。
  • 回溯:当当前路径无法到达目标时,算法会回溯到上一个决策点。
算法步骤:
  1. 选择起始节点:从图中的任意节点开始。
  2. 访问节点:标记起始节点为已访问。
  3. 探索邻接节点:对起始节点的未访问邻接节点进行探索:
    a. 将邻接节点入栈。
    b. 访问该邻接节点。
    c. 重复步骤3,直到该邻接节点的所有邻接节点都被访问。
  4. 回溯:当当前节点的所有邻接节点都被访问后,从栈中弹出该节点,回到上一个节点继续探索。
  5. 终止条件:当所有节点都被访问或找到目标节点时,搜索结束。
应用场景:
  • 路径搜索:在迷宫或图中寻找路径。
  • 拓扑排序:对有向无环图的顶点进行排序。
  • 解决数独:自动填充数独游戏。
  • 游戏AI:用于游戏如国际象棋、围棋等的算法决策。
代码实现:

DFS可以通过递归或非递归(使用栈)的方式实现。以下是使用递归的简单C语言实现示例:

#include <stdio.h>
#include <stdbool.h>

#define MAX_VERTICES 1000 // 假设最大顶点数

// 假设图使用邻接矩阵表示
int graph[MAX_VERTICES][MAX_VERTICES];
bool visited[MAX_VERTICES]; // 跟踪节点是否被访问

// 用于DFS的辅助函数
void dfs(int v, int current_depth) {
    visited[v] = true; // 标记当前节点为已访问
    printf("Visited %d at depth %d\n", v, current_depth);

    // 遍历所有未访问的邻接节点
    for (int i = 0; i < MAX_VERTICES; ++i) {
        if (graph[v][i] && !visited[i]) {
            dfs(i, current_depth + 1);
        }
    }
}

int main() {
    // 初始化图和访问数组
    // ...

    // 调用DFS从节点0开始
    dfs(0, 0);

    return 0;
}
注意事项:
  • 确保在递归实现中对每个节点只访问一次,避免无限递归。
  • 在非递归实现中,正确管理栈的使用,避免内存泄漏。
  • 对于大型图,DFS可能需要优化以减少内存消耗和提高效率。

DFS是一种强大的搜索方法,适用于从图中寻找路径或解决需要递归和回溯的问题。通过实际应用,可以加深对DFS原理的理解,并提高解决复杂问题的能力。

部分实现

判断要走的位置是否符合规定


int OutMap(int a, int b)    
{
	if (a >= 0 && a < n && b >= 0 && b < m && zmap[a][b] != '#')
		return 0;

	else
		return 1;
}

深度优先算法


int  venture(int p1, int p2, int x1, int x2)
{
	//如果箱子位置与终点位置重合则探索结束进行回溯
	if (x1 == n1 && x2 == m1)
	{
		return 1;
	}


	//分别判断此时位置的四个方向是否可走
	for (int i = 0; i < 4; i++)
	{
		//若沿此方向走合法
		if (!OutMap(p1 + people[i][0], p2 + people[i][1]))
		{
			//若沿此方向走到达位置与箱子位置重合
			if (p1 + people[i][0] == x1 && p2 + people[i][1] == x2)
			{
				//若往此方向推进箱子合法
				if (!OutMap(x1 + people[i][0], x2 + people[i][1]))
				{
					//若此方向(箱子位置改变)到达位置没有走过
					if (!jmap[p1 + people[i][0]][p2 + people[i][1]][x1 + people[i][0]][x2 + people[i][1]])   
					{
						//标记走过
						jmap[p1 + people[i][0]][p2 + people[i][1]][x1 + people[i][0]][x2 + people[i][1]] = 1;

                        //如果到达终点得到返回值1则将这一操作存进字符串并回溯
						liu = venture(p1 + people[i][0], p2 + people[i][1], x1 + people[i][0], x2 + people[i][1]);
						if (liu == 1)
						{
							road[k++] = vroad[i];
							liu = 0;
							return 1;
						}
					}
				}
			}

			//若沿此方向走到达位置与箱子位置没有重合
			else
			{
				//若此方向(箱子位置没有改变)到达位置没有走过
				if (!jmap[p1 + people[i][0]][p2 + people[i][1]][x1][x2])     
				{
					//标记走过
					jmap[p1 + people[i][0]][p2 + people[i][1]][x1][x2] = 1;
					liu = venture(p1 + people[i][0], p2 + people[i][1], x1, x2);
					//如果到达终点得到返回值1则将这一操作存进字符串并回溯
					if (liu == 1)
					{
						road[k++] = vroad[i];
						liu = 0;
						return 1;
					}
				}
			}

		}
	}

   //没有到达终点
	return 0;
}

AC代码



#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
using namespace std;


char zmap[20][20];      //地图
int  jmap[20][20][20][20] = { 0 };         //标记是否走过
int n1, m1;   //终点位置
int n2, m2;   //起点位置
int n3, m3;   //箱子位置
int n, m;     //地图大小
int  people[4][2] =         //人走的位置变化
{
	{-1,0},
	{0,1},
	{1,0},
	{0,-1},
};            
char  road[100007];        //标记人走的路径
int  k = 0;

char  vroad[4] = { 'U','R','D','L' };      //标记行走方向
int  liu = 0;     //记录到达终点以后的返回值


 //判断要走的位置是否符合规定
int OutMap(int a, int b)    
{
	if (a >= 0 && a < n && b >= 0 && b < m && zmap[a][b] != '#')
		return 0;

	else
		return 1;
}



//深度优先探索
int  venture(int p1, int p2, int x1, int x2)
{
	//如果箱子位置与终点位置重合则探索结束进行回溯
	if (x1 == n1 && x2 == m1)
	{
		return 1;
	}


	//分别判断此时位置的四个方向是否可走
	for (int i = 0; i < 4; i++)
	{
		//若沿此方向走合法
		if (!OutMap(p1 + people[i][0], p2 + people[i][1]))
		{
			//若沿此方向走到达位置与箱子位置重合
			if (p1 + people[i][0] == x1 && p2 + people[i][1] == x2)
			{
				//若往此方向推进箱子合法
				if (!OutMap(x1 + people[i][0], x2 + people[i][1]))
				{
					//若此方向(箱子位置改变)到达位置没有走过
					if (!jmap[p1 + people[i][0]][p2 + people[i][1]][x1 + people[i][0]][x2 + people[i][1]])   
					{
						//标记走过
						jmap[p1 + people[i][0]][p2 + people[i][1]][x1 + people[i][0]][x2 + people[i][1]] = 1;

                        //如果到达终点得到返回值1则将这一操作存进字符串并回溯
						liu = venture(p1 + people[i][0], p2 + people[i][1], x1 + people[i][0], x2 + people[i][1]);
						if (liu == 1)
						{
							road[k++] = vroad[i];
							liu = 0;
							return 1;
						}
					}
				}
			}

			//若沿此方向走到达位置与箱子位置没有重合
			else
			{
				//若此方向(箱子位置没有改变)到达位置没有走过
				if (!jmap[p1 + people[i][0]][p2 + people[i][1]][x1][x2])     
				{
					//标记走过
					jmap[p1 + people[i][0]][p2 + people[i][1]][x1][x2] = 1;
					liu = venture(p1 + people[i][0], p2 + people[i][1], x1, x2);
					//如果到达终点得到返回值1则将这一操作存进字符串并回溯
					if (liu == 1)
					{
						road[k++] = vroad[i];
						liu = 0;
						return 1;
					}
				}
			}

		}
	}

   //没有到达终点
	return 0;
}



//主函数
int main()
{
	//输入地图大小
	cin >> n >> m;

	//输入地图元素并标记起点、终点以及箱子的位置
	for (int i = 0; i < n; i++)
		for (int j = 0; j < m; j++)
		{
            //地图元素
			cin >> zmap[i][j];     

             //终点
			if (zmap[i][j] == '@')    
			{
				n1 = i;
				m1 = j;
			}


             //起点
			if (zmap[i][j] == 'X')     
			{
				n2 = i;
				m2 = j;
			}
            
             //箱子位置
			if (zmap[i][j] == '*')     
			{
				n3 = i;
				m3 = j;
			}


		}

	int p1 = n2, p2 = m2, x1 = n3, x2 = m3;
	venture(p1, p2, x1, x2);

	road[k] = '\0';

	//无法到达终点
	if (k == 0)
		cout << "-1";
	//到达终点输出操作
	else
	{
		for (int i = k - 1; i >= 0; i--)
			printf("%c", road[i]);
	}
}

  • 83
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值