迷宫问题

来源:互联网 发布:java mysql 存储过程 编辑:程序博客网 时间:2024/05/02 04:35
 迷宫问题就是典型的图的搜索问题,我们可以采用栈来实现迷宫的深度搜索,从而找到一条路径,但是却不是从入口到出口的最短路径;寻找最短搜索路径通常的方法是用广度优先搜索,利用队列来完成的。一般图的广度优先搜索算法可以简单描述为:

Void BFS(Graph G){

初始化一个队列;

从G的初始点开始;

if (当前的结点是目标结点)   搜索结束;

   else 初始点进入队列;

do {

取队头结点,并产生其相邻结点;

if (产生的结点是目标结点)  {输出一个解;搜索结束;}

if (产生的结点是新结点)  新结点进队列

} while (队头<=队尾);

}  // end_BFS

如果我们要得到所有的解,只需找到一个解后输出,并不结束循环就可以了。我们来看一个例子。



【问题描述】

有一个迷宫,只有一个入口和一个出口,我们希望用搜索法找到一条从入口到出口的最短路径。首先应考虑怎么描述迷宫。可用二维数组mg(m,n)存储迷宫,其含义如下:     
为了解决出界问题,我们在四周加上无法通过的“墙”,也就是“哨兵”,这样可以减少判断是否出界。图10-3是用二维数组表示的一个迷宫。



        1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

入口 &THORN; 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 1 1

        1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 1 1 1 1 1 1 1 1

        1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 1

        1 1 1 1 1 1 0 1 0 1 1 1 1 1 1 1 0 1 0 1 1 1 1

        1 0 0 0 0 0 0 1 0 1 0 0 0 1 0 1 0 1 0 1 0 0 1

        1 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 0 1 1 1 1

        1 1 0 1 0 0 0 0 0 1 0 1 0 1 0 0 0 0 0 1 0 1 1

        1 1 1 1 1 1 0 1 1 1 0 1 0 1 1 1 1 1 1 1 1 1 1

        1 1 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 1 0 1 1

        1 1 1 1 0 1 1 1 0 1 1 1 1 1 1 1 0 1 0 1 1 1 1

        1 1 0 1 0 0 0 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 1

        1 1 1 1 0 1 0 1 1 1 0 1 1 1 1 1 1 1 0 1 0 1 1

        1 0 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1

        1 1 0 1 0 1 1 1 0 1 1 1 1 1 0 1 0 1 1 1 1 1 1

        1 1 0 0 0 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 &THORN; 出口

        1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

   

                  图10-3 迷宫的数组表示

      

现在来看一看搜索路径的选择:一般的迷宫可以从八个方向进行搜索,为了简化问题,我们考虑从以下四个方向去搜索:



                                   ( i-1 , j )





                                                       

                 ( i , j -1)        ( i , j )       ( i , j +1)

                                         



                                   ( i+1 , j )



                          图 10-4 四个搜索方向



用数组表示其下标的变化(只记录其增量)如下:

                     1(上)   2(右)   3(下)   4(左)

行下标变化△i:       -1       0       1       0  

列下标变化△j:        0       1       0       -1  



为了防止被搜索过的单元被重复搜索,也就是避免转圈或走重复的路径,于是应该将所走过的路径作上记号,在不增加额外的存储空间的情况下,可将mg修改如下:


      为了找到最短路径,我们用队列SQueue来保存搜索过的位置。搜索的规则是:选择一个可通行的位置而又没有试探过的单元p,沿四个方向作搜索,若这四个方向中有可行通路又没有搜索过的单元q,则说明从p到q有通路,将q记录在SQueue中,SQueue[m*n]是一个三元组数组,用来记录搜索过的单元, SQueue[k].x记录第k次搜索的单元q的行坐标,SQueue[k].y记录q的列坐标,SQueue[k].pre记录产生q的单元即p单元在SQueue中的下标,即记录每个搜索单元的产生轨迹,或记录其父结点,其目的是为了在找到出口后沿这个下标返回到入口处,方便最后显示这条最短路径。这四个方向都搜索完了,(修改q单元为已搜索过);接下来从SQueue中取出对头,往下继续搜索。在搜索过程中如果遇上了出口单元,则认为已经找到了一条从入口到出口的最短路径;如果迷宫中所有可通行的道路都搜索完,都没有遇上出口单元,此时认为该迷宫是无法通过的。

【数据描述】

typedef struct{
int x,y; //搜索位置的坐标
int pre; //表示父结点的位置
}SQarray;
typedef struct{
int Deltai,Deltaj; //4个搜索方向的坐标变化值
}Direction;
int mg[m+2][n+2];

SQarray SQueue[Max];

Direction Dchange[5];

我们看看图10-3这样的迷宫,队列SQueue的变化情况:

1  2  3  4  5  6   7  8   9  10   11  12  13  14   15    ...

x
1
1
1
1
1
1
1
1
1
1
1
2
1
3
 
...

y
1
2
3
4
5
6
7
8
9
10
11
10
12
10
 
...

pre
0
1
2
3
4
5
6
7
8
9
10
10
11
12
 
...


                                                  &shy;             &shy;

                                                front          rear

图10-5 队列SQueue的变化

    设入口地址是(1,1),出口地址是(m,n),我们来看一看迷宫问题的算法描述。

【算法描述】

Step1:建立迷宫数组mg[m+1][n+1];建立搜索的四个方向下标变化变量:Dchange[1..4];

Step2:给mg周围加“哨兵”;

Step3: 初始化队列:front=0;rear=0;Goout=FALSE;

Step4: 入口位置入队列SQueue:rear++;SQueue[rear].x=1;SQueue[rear].y=1;SQueue[rear].pre=0; mg[1][1]=-1;(0表示入口是第一个结点,无前驱结点);

Step5: do {

    front++; i=SQueue[front].x; j=SQueue[front].y; //出队列

    for (d=1;d<=4;d++){         //沿4个方向搜索

        di=i+dchange[d].Deltai;dj=j+dchange[d].Deltaj; //确定新的搜索位置

        if (!mg[di][dj]) {       //如果新位置是未搜索通道,则将新位置入队列

           rear++; SQueue[rear].x=di;SQueue[rear].y=dj;

SQueue[rear].pre=front;

           Mg[di][dj]=-1; }     //标识搜索过的位置为-1

        if (i=m && j=n){        //找到出口

           打印路径;Goout=TRUE; }

   }while(front<=rear && !Goout)

Step6: if (!Goout) printf(“No Pass!/n”);

(7)若flag=0 则输出“无法走出迷宫”

【C源程序】

#include <stdio.h>

#define m 15

#define n 21

#define Max 500

typedef struct{

int x,y;           /*搜索位置的坐标 */

int pre;           /*表示前驱点的位置 */

}SQarray;

typedef struct{

int Deltai,Deltaj; /*4个搜索方向的坐标变化值 */

}Direction;

int mg[m+2][n+2]=

{{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},

{1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,1,1},

{1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,1,1,1},

{1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,1},

{1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,1,0,1,0,1,1,1,1},

{1,0,0,0,0,0,0,1,0,1,0,0,0,1,0,1,0,1,0,1,0,0,1},

{1,1,1,1,1,1,1,1,0,1,0,1,0,1,0,1,1,1,0,1,1,1,1},

{1,1,0,1,0,0,0,0,0,1,0,1,0,1,0,0,0,0,0,1,0,1,1},

{1,1,1,1,1,1,0,1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,1},

{1,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0,1,1},

{1,1,1,1,0,1,1,1,0,1,1,1,1,1,1,1,0,1,0,1,1,1,1},

{1,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,1,0,0,0,0,1},

{1,1,1,1,0,1,0,1,1,1,0,1,1,1,1,1,1,1,0,1,0,1,1},

{1,0,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1},

{1,1,0,1,0,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1},

{1,1,0,0,0,1,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1},

{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}};

SQarray SQueue[Max];

Direction Dchange[5]={{0,0},{-1,0},{0,1},{1,0},{0,-1}};



void print(int k)

{int i,j,kk;

kk=k;

do

{ i=SQueue[kk].x; j=SQueue[kk].y;

   mg[i][j]=3; kk=SQueue[kk].pre;

}while(kk);

printf("  模拟迷宫路径: # 表示最后走出迷宫的路径, -1表示探索过的路径/n");

printf("进入->");

for (i=1;i<=m;i++)

{ for (j=1;j<=n;j++)

     if (mg[i][j]==3) printf("%2c",'#');

       else printf("%2d",mg[i][j]);

    if (i==m) printf("->走出/n");

     else printf("/n      ");}

}



main()

{int d,i,j,di,dj,front,rear,Goout;

front=0;rear=0;Goout=0;

rear++;

SQueue[rear].x=1;SQueue[rear].y=1;SQueue[rear].pre=0; mg[1][1]=-1;

do {

    front++; i=SQueue[front].x; j=SQueue[front].y; /*出队列  */

    for (d=1;d<=4;d++){         /*沿4个方向搜索*/

   di=i+Dchange[d].Deltai; dj=j+Dchange[d].Deltaj;  /*确定新的搜索位置 */

   if (!mg[di][dj]) {          /*如果新位置是未搜索通道,则将新位置入队列*/

           rear++; SQueue[rear].x=di;SQueue[rear].y=dj;

           SQueue[rear].pre=front;

      mg[di][dj]=-1; }         /*标识搜索过的位置为-1 */

   if (i==m && j==n){          /*找到出口*/

           print(rear); Goout=1; }

   }

   }while(front<=rear && !Goout);

if (!Goout) printf("No Pass!/n");

}


【运行结果】

     模拟迷宫路径: # 表示最后走出迷宫的路径, -1表示探索过的路径

进入 –> #  #  #  #  #  #  #  #  #  # -1 -1  1 -1 -1 -1  1  0  0  0  1

         1  1  1  1  1  1  1  1  1  #  1  1  1 -1  1  1  1  1  1  1  1

         1  0  1 -1 -1 -1 -1  #  #  # -1 -1 -1 -1  1  0  0  0  1  0  0

         1  1  1  1  1 -1  1  #  1  1  1  1  1  1  1  0  1  0  1  1  1

        -1 -1 -1 -1 -1 -1  1  #  1  #  #  #  1  0  1  0  1  0  1  0  0

         1  1  1  1  1  1  1  #  1  #  1  #  1  0  1  1  1  0  1  1  1

         1  0  1 -1 -1  #  #  #  1  #  1  #  1  0  0  0  0  0  1  0  1

         1  1  1  1  1  #  1  1  1  #  1  #  1  1  1  1  1  1  1  1  1

         1  0  0  0  1  #  #  #  #  #  1  #  #  #  #  #  1  0  1  0  1

         1  1  1  0  1  1  1 -1  1  1  1  1  1  1  1  #  1  0  1  1  1

         1  0  1  0  0  0  1 -1  1  #  #  #  #  #  #  #  1  0  0  0  0

         1  1  1  0  1  0  1  1  1  #  1  1  1  1  1  1  1  0  1  0  1

         0  0  1  0  1  0  1 -1 -1  #  #  #  #  #  #  #  1  0  0  0  0

         1  0  1  0  1  1  1 -1  1  1  1  1  1 -1  1  #  1  1  1  1  1

         1  0  0  0  1  0  1 -1 -1 -1 -1 -1 -1 -1  1  #  #  #  #  #  # -> 走出



【说明】算法的时间复杂度取决于搜索单元的个数,即数组SQueue的使用大小,最坏的情况是 。

【实验题】

1.  将上题迷宫的4个搜索方向改成8个搜索方向,看看运行的结果怎样;

2.  改进上面的程序,求出所有的通路,并统计从迷宫的入口到出口共有几条通路;

3.  将队列改成栈来解决迷宫问题,并比较这两种方法的差异。