只是记录一下自己的学习过程
推箱子是一款经典游戏。这里我们玩的是一个简单版本,就是在一个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
输出
有解时,输出玩家走的每一步。当无论如何达成不了的时候,输出-1。
样例输入
6 6
…#…
…
#*##…
…##.#
…X…
.@#…
样例输出
RRUUULLULDDUURDRRURDDLDDRDLLULLUUDDRRRUUULLULLDRRRLULDDDDRRRUURUULDDDUULLULDDDDRRDRRULLLRRUUULLULDDDD
这个题目与之前的广度优先版本差不多,不过是使用的探索方法不一样以及需要返回的参数不一样,关于这个问题的分析可以去我的上一篇博客看一下链接。
另外需要注意的是回溯的时候,操作存储是逆序的,所以输出的时候也需要逆序输出。
还有,跟一般的深度探索不一样,若是没有箱子的限制条件,标记位置是否走过的数组在回溯的时候需要清零。但是这个问题,我理解为箱子跟人这样一个位置关系是独立的,所以可以不用清零节约时间,但是起点不能标记,不然回溯之后不好探索其他方向。
强行解释,没有什么道理,懂的大佬可以在评论区解释一下,谢谢。
关于深度优先探索(DFS)推荐这篇博客 链接.
#include
#include
#include
#include
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]);
}
}