0
点赞
收藏
分享

微信扫一扫

迷宫问题分析


迷宫问题是搜索(Search)的经典问题,寻路是迷宫问题的核心。在某些问题中还会增加体力值的限制。本文主要使用深度优先搜索(DFS)进行迷宫问题的求解。


深度优先搜索

深度优先搜索的思想是从一个关键点开始,沿着一条路径一直走到尽头,如果在该路径不能发现目标解,则回溯到关键位置,进行下一条路径的搜索。


​​迷宫问题 I​​

问题描述

定义一个只包含0和1的二维数组N*M,它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的路线。入口点为[0,0],既第一格是可以走的路。


输入描述:

输入两个整数,分别表示二维数组的行数,列数。再输入相应的数组,其中的1表示墙壁,0表示可以走的路。数据保证有唯一解,不考虑有多解的情况,即迷宫只有一条通道。

输出描述:

左上角到右下角的最短路径。


问题分析

该问题是一个寻找出口最短路径的问题。由于题目明确说明数据保证有唯一解,所以只需要找到某条路径可到达出口即可。这种路径搜索的问题,可以考虑用深度优先搜索解决。

另外题目要求输出起点到终点的路径坐标,由于搜索路径过程中路径节点是不断变化的(需要不断调整路径坐标),所以我们需要一个容器来存储合法路径,该容器具有后进先出的特性,所以选择进行存储。

首先进行搜索部分的代码分析,具体操作是:在当前位置进行上、下、左、右四个方向的搜索,若目标位置越界则直接返回​false​,若目标位置为终点,则返回​true​。为了避免重复搜索进入死循环,需要标记已经搜索过的位置。

 迷宫问题分析_最短路径

bool FindPath(vector<vector<int>>& maze, int row, int col, pair<int, int> pos)
{
int x = pos.first;
int y = pos.second;

if(x < 0 || x >= row || y < 0 || y >= col || maze[x][y] != 0)
{
//越界或撞墙,返回false
return false;
}
if((x == row - 1) && (y == col - 1))
{
//到达终点,一路返回true
return true;
}

maze[x][y] = 2;//标记已搜索位置
//递归搜索四个方向
bool res = FindPath(maze, row, col, pair<int, int>(x - 1, y)) ||
FindPath(maze, row, col, pair<int, int>(x + 1, y)) ||
FindPath(maze, row, col, pair<int, int>(x, y - 1)) ||
FindPath(maze, row, col, pair<int, int>(x, y + 1));

return res;
}

另外要记录路径坐标,具体操作是:搜索到一个新位置时,首先将该位置的坐标存储在栈中,若该位置不合法,则将该坐标出栈。为了调整路径方便,用一个全局变量path​进行存储。在上面代码的基础上添加记录坐标的部分代码。

bool FindPath(vector<vector<int>>& maze, int row, int col, pair<int, int> pos)
{
int x = pos.first;
int y = pos.second;
path.push(pos);//首先将坐标入栈
if(x < 0 || x >= row || y < 0 || y >= col || maze[x][y] != 0)
{
path.pop();//不合法,将坐标出栈
return false;
}
if((x == row - 1) && (y == col - 1))
{
return true;
}

maze[x][y] = 2;
bool res = FindPath(maze, row, col, pair<int, int>(x - 1, y)) ||
FindPath(maze, row, col, pair<int, int>(x + 1, y)) ||
FindPath(maze, row, col, pair<int, int>(x, y - 1)) ||
FindPath(maze, row, col, pair<int, int>(x, y + 1));

if(!res) {
//该位置的四个方向都不能到达终点,将该位置坐标出栈
path.pop();
}
return res;
}

在打印路径坐标时,由于栈先进后出的性质,要借助一个辅助栈进行倒数据和打印。

void PrintPath(stack< pair<int, int> >& pt)
{
stack< pair<int, int> > rPath;//辅助栈
//倒数据
while(!pt.empty())
{
rPath.push(pt.top());
pt.pop();
}
//打印
while(!rPath.empty())
{
cout << "(" << rPath.top().first << "," <<
rPath.top().second << ")" << endl;
rPath.pop();
}
}

完整代码

stack<pair<int, int>> path;

void MazeInput(vector<vector<int>>& maze, int row, int col)
{
for(int i = 0; i < row; i++)
{
for(int j = 0; j < col; j++)
{
cin >> maze[i][j];
}
}
}

bool FindPath(vector<vector<int>>& maze, int row, int col, pair<int, int> pos)
{
int x = pos.first;
int y = pos.second;
path.push(pos);
if(x < 0 || x >= row || y < 0 || y >= col || maze[x][y] != 0)
{
path.pop();
return false;
}
if((x == row - 1) && (y == col - 1))
{
return true;
}

maze[x][y] = 2;
bool res = FindPath(maze, row, col, pair<int, int>(x - 1, y)) ||
FindPath(maze, row, col, pair<int, int>(x + 1, y)) ||
FindPath(maze, row, col, pair<int, int>(x, y - 1)) ||
FindPath(maze, row, col, pair<int, int>(x, y + 1));

if(!res){
path.pop();
}
return res;
}

void PrintPath(stack< pair<int, int> >& pt)
{
stack< pair<int, int> > rPath;
while(!pt.empty())
{
rPath.push(pt.top());
pt.pop();
}
while(!rPath.empty())
{
cout << "(" << rPath.top().first << "," <<
rPath.top().second << ")" << endl;
rPath.pop();
}
}

int main()
{
int row = 0;
int col = 0;
cin >> row >> col;
vector<vector<int>> maze(row, vector<int>(col, 0));
MazeInput(maze, row, col);
pair<int, int> pos(0, 0);
FindPath(maze, row, col, pos);
PrintPath(path);
return 0;
}


​​迷宫问题 II​​

问题描述

小青蛙有一天不小心落入了一个地下迷宫,小青蛙希望用自己仅剩的体力值P跳出这个地下迷宫。为了让问题简单,假设这是一个n*m的格子迷宫,迷宫每个位置为0或者1,0代表这个位置有障碍物,小青蛙达到不了这个位置;1代表小青蛙可以达到的位置。小青蛙初始在(0,0)位置,地下迷宫的出口在(0,m-1)(保证这两个位置都是1,并且保证一定有起点到终点可达的路径),小青蛙在迷宫中水平移动一个单位距离需要消耗1点体力值,向上爬一个单位距离需要消耗3个单位的体力值,向下移动不消耗体力值,当小青蛙的体力值等于0的时候还没有到达出口,小青蛙将无法逃离迷宫。现在需要你帮助小青蛙计算出能否用仅剩的体力值跳出迷宫(即达到(0,m-1)位置)。

输入描述:

输入包括n+1行:
第一行为三个整数n,m(3 <= m,n <= 10),P(1 <= P <= 100)
接下来的n行:
每行m个0或者1,以空格分隔

输出描述:

如果能逃离迷宫,则输出一行体力消耗最小的路径,输出格式见样例所示;如果不能逃离迷宫,则输出"Can not escape!"。 测试数据保证答案唯一。


问题分析

本题与上一个问题相似,额外加入了 体力值 的概念。当一个矩阵被确定时,则小青蛙距离终点的水平位移即被确定,即小青蛙水平移动消耗的体力已被确定。则现在只考虑竖直方向小青蛙的移动。小青蛙向上移动一步消耗 3 体力值,向下移动不消耗体力值,为了尽量减少体力值的消耗,应尽量减少青蛙竖直移动的位移。所以问题就转化成了寻找到达终点的最短路径的问题。

解题策略为:寻找所有可行路径,在此期间记录最短路径。

与普通寻路问题不同的是,当搜索到一条可行路径时不能直接一路返回,而是回到一个 关键位置 ,在该关键位置继续进行其他路径的搜索。

需要注意的是,因为要进行重复搜索,所以当返回时,要将该位置重新置为 1 ,以使后续的搜索正常进行。

为了使读者注意到栈拷贝时的问题,本题使用C语言描述。

ST path;//记录路径
ST minPath;//记录最短路径

void FindPath(int** maze, int row, int col, int x, int y, int p)
{
int* Pos = (int*)malloc(sizeof(int) * 2);
Pos[0] = x;
Pos[1] = y;
StackPush(&path, Pos);

if (x < 0 || x >= row || y < 0 || y >= col || maze[x][y] != 1){
StackPop(&path);
return;
}

//到达终点,检查并记录目前的最短路径
if ((x == 0) && (y == col - 1))
{
if (p >= 0 && (StackEmpty(&minPath) || StackSize(&path) < StackSize(&minPath)))
{
StackCopy(&minPath, &path);
//注意此时不能直接return
//终点位置上下左右四个方向均无法到达,故执行:从终点位置向前重置路径为 1
}
}

maze[x][y] = 2;

FindPath(maze, row, col, x - 1, y, p - 3);
FindPath(maze, row, col, x + 1, y, p);
FindPath(maze, row, col, x, y - 1, p - 1);
FindPath(maze, row, col, x, y + 1, p - 1);

maze[x][y] = 1;//重新将该位置记为 1
StackPop(&path);
}

在进行栈拷贝时,不能直接进行赋地址拷贝,而需要将栈数据拷贝到目标栈中。(深拷贝)

//栈拷贝
void StackCopy(ST* sTDest, ST* sTSour)
{
StackDestory(sTDest);
StackInit(sTDest);
sTDest->data = (STDataType*)malloc(sizeof(STDataType) * StackSize(sTSour));
memcpy(sTDest->data, sTSour->data, sizeof(STDataType) * StackSize(sTSour));
sTDest->top = sTSour->top;
sTDest->capacity = sTSour->capacity;
}

在入栈时,要先检验此条路径到达终点时体力值是否大于0,否则青蛙无法到达终点,不入最小栈


完整代码

ST path;//记录路径
ST minPath;//记录最短路径

void PrintStack(ST* ps)
{
ST rPath;
StackInit(&rPath);
while (!StackEmpty(ps))
{
StackPush(&rPath, StackTopData(ps));
StackPop(ps);
}
StackDestory(ps);
ps = NULL;
while (StackSize(&rPath) > 1)
{
int* tmp = StackTopData(&rPath);
printf("[%d,%d],", tmp[0], tmp[1]);
StackPop(&rPath);
}
int* tmp = StackTopData(&rPath);
printf("[%d,%d]", tmp[0], tmp[1]);
StackPop(&rPath);

StackDestory(&rPath);
}
//栈拷贝
void StackCopy(ST* sTDest, ST* sTSour)
{
StackDestory(sTDest);
StackInit(sTDest);
sTDest->data = (STDataType*)malloc(sizeof(STDataType) * StackSize(sTSour));
memcpy(sTDest->data, sTSour->data, sizeof(STDataType) * StackSize(sTSour));
sTDest->top = sTSour->top;
sTDest->capacity = sTSour->capacity;
}

void FindPath(int** maze, int row, int col, int x, int y, int p)
{
int* Pos = (int*)malloc(sizeof(int) * 2);
Pos[0] = x;
Pos[1] = y;
StackPush(&path, Pos);

if (x < 0 || x >= row || y < 0 || y >= col || maze[x][y] != 1){
StackPop(&path);
return;
}

if ((x == 0) && (y == col - 1))
{
if (p >= 0 && (StackEmpty(&minPath) || StackSize(&path) < StackSize(&minPath)))
{
StackCopy(&minPath, &path);
//注意此时不能直接return
//终点位置上下左右四个方向均无法到达,故执行:从终点位置向前重置路径为 1
}
}

maze[x][y] = 2;

FindPath(maze, row, col, x - 1, y, p - 3);
FindPath(maze, row, col, x + 1, y, p);
FindPath(maze, row, col, x, y - 1, p - 1);
FindPath(maze, row, col, x, y + 1, p - 1);

maze[x][y] = 1;
StackPop(&path);
}

int main()
{
int row = 0;
int col = 0;
int p = 0;
scanf("%d %d %d", &row, &col, &p);

int** maze = (int**)malloc(sizeof(int*) * row);
for (int i = 0; i < row; i++){
maze[i] = (int*)malloc(sizeof(int) * col);
}

for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++){
scanf("%d", &maze[i][j]);
}
}

StackInit(&path);
StackInit(&minPath);

FindPath(maze, row, col, 0, 0, p);

if(!StackEmpty(&minPath))
{
PrintStack(&minPath);
}
else
{
printf("Can not escape!\n");
}

for (int i = 0; i < row; i++)
{
free(maze[i]);
maze[i] = NULL;
}
maze = NULL;
return 0;
}

举报

相关推荐

0 条评论