不知道有没有人和我一样,在用栈求解迷宫问题所有可行解得时候,会遇到相同路径重复输出的BUG。我看了网上问其他博客的代码,发现也有这个BUG,功夫不负有心人,花了1小时之久终于发现了BUG!
BUG如下图:
图片上的路径1,2重复了,路径4,5重复了,除去重复路径,真实的路径只有4条。接下来展示排错前的代码,也是出现上述问题大部分人的代码。
#include<stdio.h>
#include<malloc.h>
#include<time.h> //计时器,计算代码运算时间
#define inf 100 //初始化最小路径长度
const int MaxSize=1000; //栈的大小
int M=8,N=8;
//初始化迷宫,1代表障碍,0代表可通过。
int MG[10][10]={{1,1,1,1,1,1,1,1,1,1},{1,0,0,1,0,0,0,1,0,1},
{1,0,0,1,0,0,0,1,0,1},{1,0,0,0,0,1,1,0,0,1},
{1,0,1,1,1,0,0,0,0,1},{1,0,0,0,1,0,0,0,0,1},
{1,0,1,0,0,0,1,0,0,1},{1,0,1,1,1,0,1,1,0,1},
{1,1,0,0,0,0,0,0,0,1},{1,1,1,1,1,1,1,1,1,1}};
typedef struct
{
int i;//当前方块的行号
int j;//当前方的列号
int di;
}Box;
typedef struct
{
Box data[MaxSize];
int top;//栈顶指针
}SqStack;//顺序栈类型
void InitStack(SqStack *&s)//初始化栈
{
s=(SqStack *)malloc(sizeof(SqStack));
s->top=-1;
}
bool Push(SqStack *&s,Box e)//进栈
{
if(s->top==MaxSize-1)
return false;
s->top++;
s->data[s->top]=e;
}
bool StackEmpty(SqStack *s)//判断栈是否为空
{
return (s->top==-1);
}
bool Pop(SqStack *&s,Box &e)//出栈
{
if(s->top==-1)
return false;
e=s->data[s->top];
s->top--;
return true;
}
bool GetTop(SqStack *s,Box &e)//取出栈顶元素
{
if(s->top==-1)
return false;
e=s->data[s->top];
return true;
}
bool mgpath(int xi,int yi,int xe,int ye) //(xi,yi)是您迷宫的起点,(xe,ye)是终点。
{
int minn=inf,minum;
int cont=1;
int f=0;
int i,j,di,i1,j1,k;
bool _find=false;
SqStack *st;//定义栈st
InitStack(st);//初始化栈st
Box e1,e;
e1.i=xi;
e1.j=yi;
e1.di=-1;
Push(st,e1);//方块e进栈
MG[xi][yi]=-1;//将入口的迷宫值置为-1,避免重复走到该方块
while(!StackEmpty(st))//栈不为空时循环
{
GetTop(st,e);
i=e.i;
j=e.j;
di=e.di;
if(i==xe&&j==ye)//找到了出口,输出该路径
{
f=1;
if(st->top<minn)
{
minn=st->top;
minum=cont;
}
printf("迷宫路径%d为:\n",cont++);
for(k=0;k<=st->top;k++)
printf("(%d,%d)\t",st->data[k].i,st->data[k].j);
printf("\n");
Pop(st,e);
MG[e.i][e.j]=0;
}
else{
_find=false;
while(di<4&&!_find)
{
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;
}
if(_find)
{
st->data[st->top].di=di;
e.i=i1;
e.j=j1;
e.di=-1;
Push(st,e);
MG[i1][j1]=-1;
}
else
{
Pop(st,e);
MG[e.i][e.j]=0;
}
}
}
if(f==1)
{
printf("最短路径为路径%d\n",minum);
printf("最短路径长度为%d\n",minn);
return true;
}
return false;
}
int main()
{
int begin,end;
begin=clock();
if(!mgpath(1,1,3,1))
printf("该迷宫问题没有解!\n");
end=clock();
printf("\nRunning Time: %dms\n",end-begin);
return 0;
}
上述代码按逻辑来说是没问题的,当到探索达终点时,则从栈底到栈顶输出该路径上的所有结点。
这段代码有BUG的直接原因:本该出栈的方块未及时出栈。BUG就在当某个方格的4个方向都已经探索过,判断是否出栈的语句中。而出栈方块与否与变量_find直接相关。
在上述代码中,我们不妨设想一下,假如某个方块的四个方向均已探索过,find变量是否一定是false。回答是否定的,因为当某个方块di已经为3时仍能进入下图所示代码一次,di++后,di已经变成了4。但在switch中没有di==4的情况,所以对应的 i1, j1有滞后性,会等于上一个已经出栈的方块在迷宫中的值,也就是0,所以find仍为true,本该出栈却没有出栈,所以会有相同路径重复输出。
所以只要在判断_find为true or false的判断条件上再加上一句"di<=3"的判定条件,就可以解决上述BUG。
//改正前
while(di<4&&!_find)
{
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;
}
//修正后
while(di<4&&!_find)
{
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 && di<=3) _find=true;
}
我想大家之所以会想选择用栈非递归求解迷宫问题,是因为一句话“能用循环解决的就不用递归。”虽然递归有结构简单、清晰,易于阅读,方便其正确性证明的优点。但是占用内存过多,执行效率低,不易优化也是不容忽视的。
但是几乎所有的排序的高效算法都是采用了递归的分治思想,如二分归并排序、快速排序和堆排序。所以说,处理相同问题,循环一定比递归好的说法是错的。于是我对比了用递归做迷宫问题和用栈做迷宫问题的时间开销发现两者的时间开销是大致相同的。但是在汉诺塔问题中,移动10个盘片,我发现用栈比用递归做快了将近10000倍。而且随着规模增大,效率的高低悬殊更加明显。