迷宫问题
解决迷宫问题我们一般采用回溯法,即将原问题分解为若干子问题来解决,当子问题无法在继续进行下去时,就退回到上一步来搜索该位置的其他分支,若还是无法解决,就再向后退,直至找到可行解或找不到退出为止。
迷宫一般分为无环和带环、我们先来看无环迷宫
- 不带环迷宫
主要步骤:
1.检测入口
2.检测当前位置是否为通路,走当前步
up———> x-=1
left———> y-=1
right——-> y+=1
down——> x+=1
解题思路大致为:先从有效的入口进入迷宫,将走过的位置标记为2并将其压入栈中,再判断其上、左、右、下四个方向是否为通路,即是否为1,若为1就走到该位置,改标记并压栈。若走到某位置时,其上下左右四个方向都不通,说明这一步走错了,应该退回到上一步,即进行出栈操作,并将该错误位置标记改为3,防止下次再走错,让这样的过程循环起来,直至找到出口。
代码如下:
//Maze.h
#include "stack.h"//我们需要保存走过的路径,需要用到栈,因此将栈的头文件包过来
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
//定义六行六列的迷宫
#define MAX_ROW 6
#define MAX_COL 6
//用结构体将迷宫封装起来
typedef struct Maze
{
int _map[MAX_ROW][MAX_COL];
}Maze;
void InitMaze(Maze *m, int map[][MAX_COL]);
void PrintMaze(Maze *m);
int IsValidEntry(Maze *m, Position entry);
int IsPass(Maze *m, Position pos);
int IsExit(Maze *m, Position pos, Position entry);
void _PassMaze(Maze *m, Position entry, Stack *s);
void PassMazeNor(Maze *m, Position entry);
int PassMaze(Maze *m, Position cur, Position entry);
//Maze.c
#include "Maze.h"
//初始化迷宫
//即初始化地图,将二维数组对应位置的信息给过去即可
void InitMaze(Maze *m, int map[][MAX_COL])
{
int i = 0;
int j = 0;
assert(m);
for (; i < MAX_ROW; ++i)
{
for (j = 0; j < MAX_COL; ++j)
m->_map[i][j] = map[i][j];
}
}
//检测是否为有效入口
int IsValidEntry(Maze *m, Position entry)
{
if (0 == entry.x || MAX_ROW - 1 == entry.x || 0 == entry.y || MAX_COL - 1 == entry.y)
{
if (1 == m->_map[entry.x][entry.y])
return 1;
}
return 0;
}
//检测当前位置是否为通路
int IsPass(Maze *m, Position pos)
{
assert(m);
return 1 == m->_map[pos.x][pos.y];
}
//检测是否为出口
//由于入口和出口都在边界,因此要将入口位置也传过来,先检测该位置是否为入口
int IsExit(Maze *m, Position pos, Position entry)
{
//若为入口返回0
if (pos.x == entry.x&&pos.y == entry.y)
return 0;
if (0 == pos.x || MAX_ROW - 1 == pos.x || 0 == pos.y || MAX_COL - 1 == pos.y)
{
return 1;
}
return 0;
}
//非递归走迷宫
void _PassMaze(Maze *m, Position entry, Stack *s)
{
Position next;
Position pos;
//定义一个栈用来保存走过的路径
assert(m);
//若入口无效直接返回
if (!IsValidEntry(m, entry))
return;
StackInit(s);
StackPuch(s, entry);
while (!StackEmpty(s))
{
pos = StackTop(s);//取栈顶元素为当前位置
m->_map[pos.x][pos.y] = 2;//并将该位置标记为2,表示走过的路径
//若当前位置为出口则返回
if (IsExit(m, pos, entry))
return;
//up
next = pos;
next.x -= 1;
if (IsPass(m, next))
{
StackPuch(s, next);
continue;
}
//left
next = pos;
next.y -= 1;
if (IsPass(m, next))
{
StackPuch(s, next);
continue;
}
//right
next = pos;
next.y += 1;
if (IsPass(m, next))
{
StackPuch(s, next);
continue;
}
//down
next = pos;
next.x += 1;
if (IsPass(m, next))
{
StackPuch(s, next);
continue;
}
//上下左右都无法走通时,说明上一步走错了,需要向回退一步
//即从栈顶取出一个元素,并将该位置标记为3,以防再次走错
StackPop(s);
m->_map[pos.x][pos.y] = 3;
}
}
void PassMazeNor(Maze *m, Position entry)
{
Stack s;
_PassMaze(m, entry, &s);
while (StackSize(&s)>0)
{
printf("(%d,%d)<----", StackTop(&s).x, StackTop(&s).y);
StackPop(&s);
}
printf("\n");
return;
}
//打印迷宫
void PrintMaze(Maze *m)
{
int i = 0;
int j = 0;
assert(m);
for (; i < MAX_ROW; ++i)
{
for (j = 0; j < MAX_COL; ++j)
{
printf("%d ", m->_map[i][j]);
}
printf("\n");
}
}
以上我们采用的是非递归的方法走迷宫,事实上采用递归的方法解决此类问题更加方便,给出代码
//递归走迷宫
int PassMaze(Maze *m, Position cur, Position entry)
{
assert(m);
if (IsPass(m, cur))
{
Position next;
m->_map[cur.x][cur.y] = 2;
//递归出口
if (IsExit(m, cur, entry))
return 1;
//up
next = cur;
next.x -= 1;
if (PassMaze(m, next, entry))
return 1;
//left
next = cur;
next.y -= 1;
if (PassMaze(m, next, entry))
return 1;
//right
next = cur;
next.y += 1;
if (PassMaze(m, next, entry))
return 1;
//down
next = cur;
next.x += 1;
if (PassMaze(m, next, entry))
return 1;
m->_map[cur.x][cur.y] = 3;
}
return 0;
}
测试用例:
void TestMaze()
{
Maze m;
int map[MAX_ROW][MAX_COL] = {
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 0 },
{ 0, 0, 1, 0, 1, 1 },
{ 0, 0, 1, 0, 0, 0 },
};
Position entry = { 5, 2 };
InitMaze(&m, map);
PrintMaze(&m);
PassMazeNor(&m, entry, entry);
PrintMaze(&m);
运行结果:
带环迷宫
与不带环迷宫相比,带环迷宫相对复杂一点,但大体的方法类似将入口标记为2,并将该位置压栈,之后每走一步将此位置标记为上一位置所标记数加1,在判断某一位置是否为通路时,我们将标记为1和大于其前一位置的标记数最为通路的判断依据。
由于是带环迷宫,其通路肯定不止一条,因此我们需要找出走该迷宫的最短路径, 应该给两个栈,一个保存当前所走路径,另一个保存最短路径,若当前路径比最短路径短时,用当前路径栈来更新最短路径栈,最终最短路径栈中的路径即为该迷宫的最短路径
代码如下:
//判断下一个位置是否为通路
int IsNextPass(Maze *m, Position cur, Position next)
{
if (m->_map[next.x][next.y] == 1 || m->_map[next.x][next.y] > m->_map[cur.x][cur.y])
return 1;
return 0;
}
//保存最短路径
void SaveShortPath(Stack *Path, Stack *shortPath)
{
int i = 0;
for (; i < StackSize(Path); ++i)
{
shortPath->_array[i] = Path->_array[i];
}
shortPath->_size = Path->_size;
}
//获取最短路径
void _GetShortPath(Maze *m,Position cur,Position entry, Stack * Path, Stack * shortPath)
{
Position next;
//递归出口
if (IsExit(m,cur,entry))
{
//将出口位置也压入栈中
StackPuch(Path, cur);
//若保存最短路径的栈为空或是当前路径栈的元素个数小于最短路径栈的元素个数
//则用当前栈来更新最短栈
if (StackEmpty(shortPath) || StackSize(Path) < StackSize(shortPath))
{
SaveShortPath(Path, shortPath);
}
//弹出出口位置元素,继续寻找其他通路
StackPop(Path);
return;
}
//将入口位置置为2
if (StackEmpty(Path))
m->_map[cur.x][cur.y] = 2;
//压入当前路径栈
StackPuch(Path, cur);
//up
next = cur;
next.x -= 1;
//若下一位置为通路,则用当前位置加一来更新下一位置,并且走下一步
if (IsNextPass(m, cur, next))
{
m->_map[next.x][next.y] = m->_map[cur.x][cur.y] + 1;
_GetShortPath(m, next, entry, Path, shortPath);
}
//left
next = cur;
next.y -= 1;
if (IsNextPass(m, cur, next))
{
m->_map[next.x][next.y] = m->_map[cur.x][cur.y] + 1;
_GetShortPath(m, next, entry, Path, shortPath);
}
//right
next = cur;
next.y += 1;
if (IsNextPass(m, cur, next))
{
m->_map[next.x][next.y] = m->_map[cur.x][cur.y] + 1;
_GetShortPath(m, next, entry, Path, shortPath);
}
//down
next = cur;
next.x += 1;
if (IsNextPass(m, cur, next))
{
m->_map[next.x][next.y] = m->_map[cur.x][cur.y] + 1;
_GetShortPath(m, next, entry, Path, shortPath);
}
//每次回退时需要弹出栈顶元素
StackPop(Path);
}
void GetShortPath(Maze *m, Position entry)
{
Stack Path, shortPath;
if (!IsValidEntry(m,entry))
return;
StackInit(&Path);//当前路径栈
StackInit(&shortPath);//最短路径栈
_GetShortPath(m, entry,entry, &Path, &shortPath);
while (!StackEmpty(&shortPath))
{
printf("<%d,%d><---", StackTop(&shortPath));
StackPop(&shortPath);
}
printf("\n");
}
测试用例:
void TestMaze()
{
Maze m;
int map[MAX_ROW][MAX_COL] = {
{ 0, 0, 0, 0, 0, 0 },
{ 0, 1, 1, 1, 0, 0 },
{ 0, 1, 0, 1, 0, 0 },
{ 0, 1, 0, 1, 0, 0 },
{ 0, 1, 1, 1, 1, 1 },
{ 0, 1, 0, 0, 0, 0 },
};
InitMaze(&m, map);
PrintMaze(&m);
Position entry = { 5, 1 };
GetShortPath(&m, entry);
PrintMaze(&m);
}
运行结果: