迷宫问题两种算法

 1)问题描述:
给定一个M*N的迷宫图,求一条从指定入口到出口的路径。假设迷宫用下图表示。对于每一个方块空白表示通道,阴影表示墙。所求路径必须是简单路径,即在求得的路径上不能重复出现同一个通道块。

2)数据组织:
为了表示迷宫,设置一个数组mg,其中每一个元素表示一个方块的状态,为0是表示是通道,否则就不可走。为了算法方便,在一般的外面加了一道墙。上图所示的迷宫对应的数据组mg如下:
byte  maze[M+1][N+1] =
{  
 {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}
};

另外在算法中用到的栈采用顺序栈存储结构,即将栈定义为:

#include <stdio.h> typedef char byte; typedef struct st{ int i,j; /*当前方块的行号和列号*/1 byte di; /*di是下一可走相邻方位的方位号*/ }st; int top;

 

 

并将栈顶指针top 初始化为-1;

3)设计运算算法:

求解迷宫问题就是在一个指定的迷宫中求出从入口到出口的路径。在求解时,通常用的是“穷举求解法”,即从入口出发,顺某一方向向前试探,若能走通,则继续往前走;否则原路返回,换到下一个方向继续试探,知道所有可能的路径都试探完为止。

 为了保证在任何位置上都能沿原路返回(回溯),需要用一个后进先出的栈来保存从入口到当前位置的路径。

对于迷宫的每个方块,有上下左右四个方块相邻,如下图所示:

第i行第j列的当前方块的位置记为(i,j),规定上方方块是0,并按顺时针方向递增编号。在试探过程中,假设从方位0到方位3的方向查找下一个可走的方块。

求解迷宫(xi,yi)到(xe,ye)路径的过程是:先将入口进栈(其初始值是-1 ),在栈不空时循环–取出栈顶方块(不退栈),若该方块是出口则输出栈中所有方块即为路径;否则找下一个可走的相邻方块,若不存在这样的方块,说明当前路径不可能走通则回溯,也就是回复当前方块为0后退栈。若存在这样的方块,则将其保存到栈顶元素中,并将这个可走的相邻方块进栈(其初始位置设置为-1)

 为了保证试探的可走相邻方块不是已走路径上的方块,如(i,j)已进栈,在试探(i+1,j)的下一可走方块时,又试探到(i,j),这样可能就引起死循环,为此在一个方块进栈后,将相应的mg数组元素值改为-1(变为不可走的相邻方块),当退栈时(表示该栈顶方块没有可走相邻方块),将其回复为0.

对应的算法如下:

#include <stdio.h> typedef char byte; typedef struct st{ int i,j; /*当前方块的行号和列号*/1 byte di; /*di是下一可走相邻方位的方位号*/ }st; st stk[500]; int top = -1; //top = -1; byte maze[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} }; void findPath (int xi,int yi,int xe,int ye ) { int i,j,cnt; byte di,find; top ++; //初始方块即入口进栈 stk[top].i = xi; stk[top].j = yi; stk[top].di = -1; maze[xi][yi] = -1; while (top > -1 ) //栈不空是循环 { i = stk[top].i ; j = stk[top].j ; di = stk[top].di; //取出栈顶方块 if (i == xe && j == ye ) //找到出口输出路径 { cnt = 0; for (i = 0;i <= top;i ++ ) { printf(“( %d ,%d ) “,stk[i].i ,stk[i].j ); if ( (++cnt % 5 ) == 0 ) printf(“/n”); } printf(“/n”); return ; } find = 0; di ++; while (di <4 ) //找(i,j)方块的下一个可走方块 { //将其它两处的di++改为这里的一处di++将导致算法错误,想想为什么? switch (di ) { case 0: i = stk[top].i – 1; j = stk[top].j ; break; case 1: i = stk[top].i ; j = stk[top].j + 1; break; case 2: i = stk[top].i + 1; j = stk[top].j ; break; case 3: i = stk[top].i ; j = stk[top].j -1 ; break; } if (maze[i][j] == 0 ) //找到了一个可走的相邻方块 { stk[top].di = di; //修改原栈顶元素的di值 top ++; //将可走的相邻方块进栈 stk[top].i = i; stk[top].j = j; stk[top].di = -1; find = 1; maze[i][j] = -1; //为避免重复走到该方块,将其置为-1 break; } di ++; } if ( !find ) //没有相邻方块可走,则退栈 { maze[stk[top].i ][stk[top].j ] = 0 ; //让该位置变为其它路径可走方块 top –; //将该方块退栈 } } return ; } int main () { findPath (1,1,8,8); return 0; }

 

———————————————————————————————————————
对于上述问题还可以用队列来解决。使用一个顺序保存qu走过的方块,该队列的结构如下:

typedef  struct   st{
 int  i,j;     //方块的位置
 int  pre;     //指向本路径中上一个方块在队列中的下标
}st;

这里使用的队列不是环形队列,因此在出队时不会将出队元素真正从队列中删除,因为要利用他们输出路径。

3))
设计运算算法:
搜索从(xi,yi)到(xe,ye)路径的过程是:首先将(xi,yi)进队,在队列不空是循环–出队一次(由于不是环形队列该元素仍在队列中),成该出队的方块为当前方块,front为该方块在队列中的下标。如果当前方块是出口则按入口到出口的次序输出该路径并结束。

否则则按照顺时针方向找出当前方块的四个方位中可走的相邻方块(对应的数组值为0 ),将这些可走的相邻方块均插入到队列中,其pre设置为本搜索路径中上一方块在队列中的下标,也就是当前方块的front值,并将相邻方块对应的数组值置为-1,以避回过来重复搜索。如此队列为空,表示未找到出口,即不存在路径。

实际上本算法思想是从(xi,yi)开始利用队列的特点,一层一层的向外扩展可走的点,直到找到出口为止,这个方法就是广度优先搜索方法。

在找到路径后,输出路径的过程是:根据当前方块(即出口,在队列中的下标为front)的pre值可找到上一个方块,再根据上一个方块的pre值找到上上个方块如此类推直到找到所有的方块。

对应的算法如下:

void print (int front ) { int temp; int cnt; int frontT; frontT = front; while ( 1 ) //找到每一个方块并将其pre成员赋值-1. { temp = que[front].pre ; if (temp == -1 ) break; que[front].pre = -1; front = temp; } cnt = 0; for (temp = 0;temp <=frontT;temp ++ ) { if (que[temp].pre == -1 ) { printf(“( %d , %d )”,que[temp].i ,que[temp].j ); if ( ( ++cnt % 5 ) == 0 ) printf(“/n”); } } } void findPath (int xi,int yi,int xe,int ye ) { int i,j,cnt; byte di,find; rear ++; que[rear].i = xi; que[rear].j = yi; que[rear].pre = -1; maze[1][1] = -1; //将其赋值-1,以避免回过来重复搜索。 while (front < rear ) //队列不空且未找到路径时循环 { front ++; //出队但仍在队列中 i = que[front].i ; j = que[front].j ; if (i == xe && j == ye ) //找到了出口输出路径 { print (front ); return ; } // find = 0; for (di = 0;di <= 3;di ++ ) //循环扫描每个方位,把每个可走的方块插入队列中 { switch (di) { case 0: i = que[front].i – 1; j = que[front].j ; break; case 1: i = que[front].i ; j = que[front].j + 1; break; case 2: i = que[front].i + 1; j = que[front].j ; break; case 3: i = que[front].i ; j = que[front].j – 1; break; } if (maze[i][j] == 0 ) { rear ++; //将该相邻方块插入到队列中 que[rear].i = i; que[rear].j = j; que[rear].pre = front; //指向路径中上一个方块的下标 maze[i][j] = -1; //将其赋值-1,以避免回过来重复搜索。 } } } } int main () { findPath (1,1,8,8); return 0; }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

    原文作者:迷宫问题
    原文地址: https://blog.csdn.net/gudujianjsk/article/details/5445100
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞