假期连载之五

(迷宫Maze的魅力——一个初级迷宫解法的讨论及延伸A

      

       现在,我们根据以上连载中的内容来研究一个有趣的应用问题。

       假设在一个二位数组maze[m+2][p+2]表示的迷宫中,除maze[1][0]以及maze[m][p+1]分别表示入口、出口外,包括第0m+1行、第0p+1列的元素均表示迷宫的围墙,用1表示,其它元素同样用1表示围墙,0表示通路,如下图例:

       1     1     1     1     1     1

       0     0     1     1     0     1

       1     0     0     1     1     1

       1     1     0     1     0     1

       1     0     1     0     0     0

       1     1     1     1     1     1

       以上是一个6*6的迷宫图示,其中(1,0)(4,5)表示了迷宫的出口和入口。对于迷宫中除边界外的任意元素maze[i][j],有0<i<m0<j<p,并且当maze[i][j]==0时路通,maze[i][j]==1时不通。我们假设迷宫中的个体一次可移动八个方向——NNEESESSWWNW,其数据关系据此可知。我们先根据可前进的方向建立一个前进方向表,给出各个方向的偏移量。

       struct offsets

{

       int a,b;          //ab分别表示在xy方向上的偏移量。

       char *dir;       //dir为方向。

};

offsets move[8];   //各个方向的偏移量表。

在使用上述数据结构时,需要初始化move[8]中8个方向的元素值,如果该前进方向走不通,则在前进路径上回退一步,再尝试其他的允许方向。

同时为了防止重走原路,另外设置一个标志矩阵mark[m+2][p+2],它的所有元素都初始化为0,一旦进行到迷宫的某个位置[i][j],则将mark[i][j]置为1,下次这个位置则被屏蔽。下面给出解决迷宫问题的递归算法C++语言描述:

int SeekPath(int x,int y)

//在本例中我们首先从Maze[1][1]开始探索,如果找到出口则返回1,否则返回0;

{

       int i,g,h;

       char *d;                                                          //g,h记录位置信息,dir记录方向

       if(x==n&&y==p)

              return 1;

       for(i=0;i<8;i++)                                               //每次选择一个方向探索

       {

              g=x+move[i].a;

              h=y+move[i].b;

              d=move[i].dir;

              if(Maze[g][h]==0&&mark[g][h]==0)

              {

                     mark[g][h]=1;

                     if(SeekPath(g,h))                             //选择此位置递归探索

                     {

                            cout<<”(”<<g<<”,”<<h<<”),”<<”Direction ”<<dir<<endl;

                            //此处输出路径的顺序是逆向的

                            return 1;

}

}

}

if(x==1&&y==1)

       cout<<”no path in Maze”<<endl;

return 0;

}

我们将上述迷宫数据经由此算法检查,g++给出了如下输出:

 

(4,4),Direction W

(4,5),Direction SE

(3,4),Direction NE

(4,3),Direction SE

(3,2),Direction S

(2,2),Direction SE

(1,1),Direction E

 

由于算法本身的问题,输出逻辑上存在一些偏差,但并不影响其正确性。

现在,我们对递归算法作出一些改进,使其能通过栈处理的方式提高算法性能。为了达到这一目标,我们需要设置一个三元组来存储已走过路径的信息,如下所示:

struct items

{

       int x,y,dir;             //位置和前进方向的序号

}

此算法的原理是,我们对当前位置的信息进行记录并压栈,按照前进方向表给出的数据向某一个允许的方向前进一位,并将此位置压栈,继续进行上述过程。如果当前位置无法走通,则将前进方向退栈,并在前进路径上回退一步,并尝试其它方向,如果栈空则表示已回退到开始位置。

下面给出迷宫非递归算法的C++描述,需要提前说明的是,我们已假设存在一个完整的Stack类,并具备连载四包含的所有栈操作函数。

int path(int m,int p)                    //关于参数的介绍请借鉴上例

{

       int i,j,d,g,h;

       mark[1][0]=1;                            //假定(1,0)为入口

       Stack<items>st(m*p);         //初始化栈st,其大小为m*p,这是根据迷宫空间设置的最大可能值。

       items tmp;                          //选择当前位置信息

       tmp.x=1;

       tmp.y=0;

       tmp.dir=2;

       st.Push(tmp);                            //将初始位置压栈

       while(st.IsEmpty()==FALSE)

       {

              st.Pop(tmp);                //将栈顶位置信息弹出,并设置为当前位置

              i=tmp.x;

              j=tmp.y;

              d=tmp.dir;

              while(d<8)                   //选择前进方向

              {

                     g=i+move[d].a;

                     h=j+move[d].b;

                     if(g==m&&h==p)

                     {//已找到出口

                            cout<<st;      

//这里在Stack类的内部重载了操作符<<,用于输出st中保存的路径

                            cout<<m<<” “<<p<<endl;

                            return;

                     }

                     if(Maze[g][h]==0&&mark[g][h]==0)

                     {//选择新的可通位置并前进

                            mark[g][h]=1;

                            tmp.x=i;

                            tmp.y=j;

                            tmp.dir=d;

                            st.Push(tmp);

                            i=g;

                            j=h;

                            d=0;

                     }

                     else d++;

              }

              cout<<”no path Maze”<<endl;

       }

};

在上述算法中,由于涉及到了栈的操作,可能会使算法难度增加,但事实证明其在性能及智能化的扩展方面存在很大的提高。对于小型、简单的二维迷宫,均可根据以上算法对其进行有效的处理。

在今后涉及到树或图部分的内容时,我们将再次回顾迷宫Maze问题,届时的迷宫实例将具备更多复杂的元素以及更巧妙的规则,使当前的算法复杂度大幅提高,也不仅仅是一般数据结构或算法就能够轻松解决的。

 

(未完待续)