完整的代码可在CSDN的资源中输入利用链式栈结构求迷宫问题所有解:回溯算法,两种输出形式数组输出和三元组输出或者顺序栈求迷宫所有解:回溯算法,两种输出方式数组和三元组进行下载
解决迷宫问题的常用算法就是回溯法,基本思想就是:从起点出发,顺着一个方向探索,若能够走通,则继续往前走,若不能够走通,则沿原路退回一步,换一个方向继续探索,直至找到一个可行的通路。此时从可行通路的到达终点的前一步进行回溯,即先从终点退一步到达终点前的第一个位置,若该位置的四个方向还没有探索完,则换一个方向继续探索,若四个方向均探索完,则再退一步到达终点前的第二个位置,若该位置的四个方向还没有探索完,则换一个方向继续探索,若四个方向均探索完,则再退一步到达终点前的第三个位置,如此判断下去直至起点的四个方向都探索完,则所有的解也就找到了。
需要注意的地方是很多同学一上手就开始进行回溯算法的设计而忽略了迷宫可能存在的两种不同的情况:
1、起点和终点不相同,此时得到的路线应该是一条折线。
2、起点和终点形同,此时得到的路线应该是一个闭合的环路。
(1)要考虑如何避免把起点压入栈之后,误把迷宫起始位置当作是终点位置而没有进行路径的查找直接就以为找到了一条路径的情况。
(2)还要考虑如何避免出现起点->起点的(上、下、左、右)->终(起)点,这种非闭合环路的情况。
我们将迷宫中走过的位置压入桟中,当找到一条路径时,从桟底到栈顶就是迷宫的一条通路。
为了表明迷宫的各个位置是否可以通过,是否在桟中以及它的四个方向的遍历情况,我们设置一个标志数组,其含义如下:
typedef struct{
int canpass; //该位置是否可以通过
int instack; //该位置是否已经在栈中存在
int direction;//改位置的四个方向遍历到哪个方向,取值为1,2,3,4分别代表该位置的东南西北方向
}Flag;
Flag cellarray[ ROW ][ COL ];
对每个位置的四个方向的遍历我们总是从东(1)开始以北(4)结束。
从起点出发,将起点入栈,并将其对应的标志数组的direction项设置为1,向东进行探索,若能够走通,则继续往东走,若不能够走通,则沿原路退回一步,向南继续探索并将direction项设置为2,直至找到一个可行的通路。此时桟底为起点,栈顶为终点,从桟底到栈顶形成了第一条迷宫的通路。开始回溯:先将终点从桟中弹出,对栈顶的位置的标志数组的direction项进行判断,若direction项还不为4表明该位置的四个方向还没有探索完,则direction++换一个方向继续探索直至到达终点然后继续从终点开始回溯或者从该位置没有一条可以到达终点的通路,当direction项等于4说明该位置的所有方向都已经探索完毕,从该位置到终点的可能的通路都已经找到,则将该位置弹出桟,继续对栈顶位置的direction进行判断如此判断下去直至起点的四个方向都探索完,则所有的解也就找到了。
//桟的数据结构
typedef struct{
int row;
int col;
}LElemType;
typedef struct temp{
struct temp *link;
LElemType data;
}Lstack;
void main()
{ //数组元素为1表示不能通过,即为墙壁,为0则可以通过
int mazearray[ ROW ][ COL ] ={ //第一个迷宫测试数组,使用不同的数组时记得修改对应的define的ROW和COL的值
{ 0,0,0,0,0,0 },
{ 0,1,1,1,1,0 },
{ 0,1,1,0,1,0 },
{ 0,1,1,1,1,0 },
{ 0,1,1,1,1,0 },
{ 0,0,0,0,0,0 }
};
/*
int mazearray[ ROW ][ COL ] ={ //第二个迷宫测试数组
{ 0,0,0,0,0,0,0,0,0,0 },
{ 0,1,1,0,1,1,1,0,1,0 },
{ 0,1,1,0,1,1,1,0,1,0 },
{ 0,1,1,1,1,0,0,1,1,0 },
{ 0,1,0,0,0,1,1,1,1,0 },
{ 0,1,1,1,0,1,1,1,1,0 },
{ 0,1,0,1,1,1,0,1,1,0 },
{ 0,1,0,0,0,1,0,0,1,0 },
{ 0,0,1,1,1,1,1,1,1,0 },
{ 0,0,0,0,0,0,0,0,0,0 }
};
*/
LElemType start;
LElemType end;
int i,j;
for( i = 0; i < ROW; i++ ){
for( j = 0;j < COL; j++)
printf("%3d", mazearray[i][j]);
printf("\n");
}
//由于显示长度有限,如果使用数组形式打印,前面部分会看不到,因此为了看到全部路径的输出,可在MazePath函数中将打印数组形式注释掉
start.row = 1;
start.col = 1;
end.row = 3;
end.col = 3; //第一个6*6数组对应的迷宫的终点位置,用于测试起点和终点不同的情况
printf("end different form start:start[%d,%d]end[%d,%d]\n", start.row, start.col, end.row, end.col);
MazePath( mazearray, start, end );
end.row = 1; //第一个6*6的数组对应的迷宫的终点位置,用于测试起点和终点相同的情况,
end.col = 1;
printf("end equals start:start[%d,%d]end[%d,%d]\n", start.row, start.col, end.row, end.col);
MazePath( mazearray, start, end );
}
//一个位置的东南西北表示为1,2,3,4
void MazePath( int maze[ ROW ][ COL ], LElemType start, LElemType end )
{
typedef struct{
int canpass;
int instack;
int direction;
}Flag;
Flag cellarray[ ROW ][ COL ]; //标志位数组
Lstack *stack; //指向栈的头元素
LElemType curpos; //当前位置
LElemType e;
int curstep = 0; //第curstep条通路
int i,j;
for( i = 0; i < ROW; i++ ){ //初始化标志位数组
for( j = 0; j < COL; j++ ){
cellarray[i][j].direction = 1;
if ( maze[i][j] )
{
cellarray[i][j].canpass = TRUE; //若位置数组对应的元素为1,则表示能通过
}
else
{
cellarray[i][j].canpass = FALSE; //若位置数组对应的元素为0,则表示不能通过
}
cellarray[i][j].instack = FALSE; //初始化时所有的位置均不在栈中
}
}
stack = InitStack( ); //初始化栈空间
curpos.row = start.row;
curpos.col = start.col;
do{
if( cellarray[curpos.row ][curpos.col].canpass && !cellarray[curpos.row ][curpos.col].instack){//如果curpos所在的位置可以通过且该位置不在栈中
cellarray[curpos.row ][curpos.col].instack = TRUE;//将curpos所在的位置压栈
Push( stack, curpos );
if( start.col == end.col && start.row == end.row )//当迷宫的起点位置和终点位置相同时,将起点位置的instack置0,但此时起点位置实际上是位于栈中的
cellarray[start.row ][start.col].instack = FALSE;
//Length( stack ) != 1是为了防止迷宫起始和结束位置相同时把第一个压入栈的起点位置误以为是终点位置而开始打印
if( curpos.row == end.row && curpos.col == end.col && Length( stack ) != 1 ){ //如果当前位置是迷宫的结束位置且此时栈中元素数目不止一个
// Length( stack ) == 3 是为了防止迷宫起始和结束位置相同出现路径不为闭合和多边形的情况
if( Length( stack ) == 3 ){ //如果栈长度为3且栈顶元素与栈底元素相同,说明出现了[1][1]->[1][2]->[1][1]类似的情况
GetTop( stack, &e ); //即路径不为闭合和多边形,出现这种请款时应弹出栈顶元素,并从新的栈顶元素开始继续循环
if( e.row == start.row && e.col == start.col ){
Pop( stack, &e );
cellarray[e.row ][e.col].instack = FALSE;
GetTop( stack, &curpos );
continue;
}
}
curstep += 1;
printf( "第%d条通路\n", curstep ); //由于链式栈的第一个元素为栈顶,因此打印的结果应从后往前看
PathPrint_Array( stack ); //数组形式打印路径,为了看到所有路径的输出,可将该行注释掉
PathPrint_Tuple( stack ); //二元组形式打印路径
Pop( stack, &e ); //每得到一条路径,就将终点弹出栈,并将终点的instack设为0
cellarray[e.row ][e.col].instack = FALSE;
GetTop( stack, &curpos ); //把弹栈后的栈顶元素作为当前位置,重新开始循环(回溯)
continue;
}
curpos.col = curpos.col + 1;//
}
else{ //若当前位置不可通过或已经在栈中
GetTop( stack, &e );
while( cellarray[e.row ][e.col].direction == 4 && !StackEmpty( stack ) ){ //如果栈顶元素对应的direction标志位为4且栈非空
cellarray[e.row ][e.col].direction = 1; // 将弹出的元素对应的位置的direction标志位重置为1
Pop( stack, &e ); //弹出当前的栈顶元素并赋给e,并将e对应的位置的instack标志位置0
cellarray[e.row ][e.col].instack = FALSE;
if( StackEmpty( stack ) )//若栈非空,即回溯结束,起点位置的四个方向均已遍历完,则程序执行结束
break;
else //否则将当前栈顶元素赋给e
GetTop( stack, &e );
}
if( cellarray[e.row ][e.col].direction < 4 ){ //此时的e为栈顶元素,如果栈顶位置的四个方向还没有遍历完
cellarray[e.row ][e.col].direction++; //将该位置的direction加1,即继续对下一个方向进行判断
if( cellarray[e.row ][e.col].direction == 2 ){ //若direction为2,则下一个位置为当前位置下方的元素:列不变,行加一
curpos.row = e.row + 1;
curpos.col = e.col;
}
else if( cellarray[e.row ][e.col].direction == 3 ){ //若direction为3,则下一个位置为当前位置左方的元素:行不变,列减一
curpos.row = e.row;
curpos.col = e.col - 1;
}
else{ //若direction为4,则下一个位置为当前位置上方的元素:列不变,行减一
curpos.row = e.row - 1;
curpos.col = e.col;
}
}
}
}while( !StackEmpty( stack ) );
DestroyStack( stack );
}