一、问题描述
给定一个MxN的迷宫图,求一条从指定入口道出口的迷宫路径,在行走中一步只能从当前方块移动到上、下、左、右相邻方块中的一个方块。
一般情况下,所求的迷宫路径为简单路径,即在求得的路径上不会重复出现同一个方块。一个迷宫图的迷宫路径可能有多条,这些路径有长有短,这里仅考虑用栈求一条从指定入口到出口的迷宫路径。
二、数据组织
为了表示迷宫,设置一个数组mg,其中每个元素表示一个方块的状态,为0时表示对应方块是通道,为1时表示对应方块是障碍物。为了算法方便,一般在迷宫外围加一条围墙。
另外,在算法中用到的栈采用顺序栈结构储存,即将迷宫栈声明如下:
typedef struct {
int i; //当前方块的行号
int j; //当前方块的列号
int di; //di是走到下一相邻可走方块的方位号
}Box; //方块类型
typedef struct {
Box data[MaxSize];
int top; //栈顶指针
}StType; //顺序栈类型
三、设计运算算法
对于迷宫中的每个方块,有上、下、左、右4个方块相邻,第i行第j列的当前方块的位置记为(i,j),规定上方方块为方位0,并按顺时针方向递增编号。在试探过程中,假设按从方位0到方位3的方向查找下一个可走的相邻方块。
求迷宫问题就是在一个指定的迷宫中求出从入口到出口的一条路径。在求解时采用“穷举法”,即从入口出发,按方位0到方位3的次序试探相邻的方块,一旦找到一个可走的相邻方块就继续走下去,并记下所走的方位;若某个方块没有相邻的可走方块,则沿原路退回到前一个方块,换下一个方位再继续试探,直到所有可能的通路都试探完为止。
为了保证在任何位置上都能沿原路退回(称为回溯),需要保存从入口到当前位置的路径上走过的方块,由于回溯的过程是从当前位置退到前一个方块,体现出后进先出的特点,所以采用栈保存走过的方块。
若一个非出口方块(i,j)是可走的,将它进栈,每个刚进栈的方块,其方位di置为-1(表示尚未试探它的周围),然后开始从方位0到方位3试探这个栈顶方块的四周,如果找到某个方位d的相邻方块(i1,j1)是可走的,则将栈顶方块(i ,j)的方位di置为d ,同时将方块(i1,j1)进栈,再继续从方块(i1,j1)做相同的操作。若方块(i ,j)的四周没有一个方位是可走的,将它退栈,前一个方块( x,y)变成栈顶方块,再从方块(x,y)的下一个方位继续试探。
在算法中应保证试探的相邻走方块不是已走路径上的方块。如方块(i,j)已进栈,在试探方块(i+1,j)的相邻可走方块时又会试探到方块(i,j)。也就是说,从方块(i,j)出发会试探方块(i+1,j),而从方块(i+1,j)出发又会试探方块(i,j),这样可能会引起死循环,为此在一个方块进栈后将对应的mg数组元素值改为-1(变为不可走方块),当退栈时(表示该栈顶方块没有相邻可走方块)将其恢复为0。
求解迷宫中从入口(xi,yi)到出口(xe,ye)的一条迷宫路径的过程如下:
将入口(xi,yi)进栈(其初始方位设置为-1);
mg[xi][yi] = -1;
while(栈不空){
取栈顶方块(i,j,di);
if((i,j)是出口(xe,ye))
{ 输出栈中的全部方块构成一条迷宫路径;
return true;
}
查找(i,j,di)的下一个相邻可走方块;
if(找到一个相邻可走方块)
{
该方块的位置为(i1,j1),对应方位为d;
将栈顶方块的di设置为d;
(i1,j1,-1)进栈;
mg[i1][j1] = -1;
}
if(没有找到(i,j,di)的任何相邻可走方块)
{
将(i,j,di)出栈;
mg[i][j] = 0;
}
}
return false;
根据上述过程得到求迷宫问题的算法如下:
bool mgpath(int xi, int yi, int xe, int ye) { //求解路径为(xi,yi)->(xe,ye)
Box path[MaxSize], e;
int i, j, di, i1, j1, k;
bool find;
StType* st; //定义栈st
InitStack(st); //初始化栈顶指针
e.i = xi; //设置e为入口
e.j = yi;
e.di = -1;
push(st,e); //方块e入栈
mg[xi][yi] = -1; //将入口的迷宫值置为-1,避免重复走到该方块
while (!StackEmpty(st)) { //栈不空时循环
GetTop(st, e); //取栈顶元素e
i = e.i;
j = e.j;
di = e.di;
if (i == xe && j == ye) { //找到了出口,输出该路径
printf("一条迷宫的路径如下:\n");
k = 0; //k表示路径中的方块数
while (!StackEmpty(st)) {
Pop(st, e); //出栈方块e
path[k++] = e; //将e添加到path数组中
}
while (k>0){
printf("\t(%d,%d)", path[k - 1].i, path[k - 1].j);
if ((k + 1) % 5 == 0) //每输出5个方块后换行
printf("\n");
k--;
}
printf("\n");
DestroyStack(st); //销毁栈
return true; //输出一条迷宫路径后返回true
}
find = false;
while (di < 4 && !find) { //找到方块(i,j)的下一个相邻方块(i1,j1)
di++;
switch (di){
case 0:i1 = i - 1; j1 = j; break;
case 1:i1 = i; j1 = j + 1; break;
case 2:i1 = i + 1; j1 = j; break;
case 3:i1 = i; j1 = j - 1; break;
}
if (mg[i1][j1] == 0)find = true; //找到一个相邻可走方块,设置find为真
}
if (find) { //找到一个相邻可走方块(i1,j1)
st->data[st->top].di = di; //修改原栈顶元素的di值
e.i = i1;
e.j = j1;
e.di = -1;
push(st, e); //相邻可走方块e进栈
mg[i1][j1] = -1; //将(i1,j1)迷宫值置为-1,避免重复走到该方块
}else { //没有路径可走,则退栈
Pop(st, e); //将栈顶方块退栈
mg[e.i][e.j] = 0; //让退栈方块的位置变为其他路径的可走方块
}
}
DestroyStack(st); //销毁栈
return false; //表示没有路径可走,返回false
}