迷宫问题的探讨
1.研究背景
前几天,有同学问我,这道题目怎么做啊。哪道题目呢?那你就继续看吧。
2.真题重现
(1)题目描述:
在一家博物馆里,有一件珍贵的珠宝放在一个房间里。博物馆的房间以NxM网格的形式呈现。博物馆的所有都与相邻的房间相邻。有一名叫山姆的小偷打算偷这件珠宝。他设法弄到了一些房间(包括放置宝石的房间)的钥匙。他从网格左上角的单元格所表示的房间进入博物馆。山姆有钥匙的房间表示为1,山姆没有钥匙的房间表示为0,有珠宝的房间表示为9.
写一个算法,如果山姆到达有珠宝的房间,输出1,否则输出0。
(2)输入和输出:
输入:
该函数/方法 的输入包括三个参数--------
1)rows,一个整数,表示网格grid中的行数(N);
2)columns,一个整数,表示网格grid中的列数(M);
3)grid,表示一个二维的整数网格。
输出:
如果山姆到达有珠宝的房间,则返回整数1,否则返回整数0。
(3)注意:
山姆不能对角移动。
(4)示例1:
输入:
3,3,{ {1,1,1},{9,1,1}, {0,1,0}}
期望的输出:1
(5)示例2:
输入:
3,3,{{0,0,0},{9,1,1},{0,1,1}}
期望的输出:0
3.数学表达
在平面直角座标系的第一象限内,我们来探讨如下问题。对于给定的两个正整数M(M>100)、N(N>100),在第一象限内有任意一点(x,y),满足x,y为正整数,且x<M,y<N,有f(x,y)=0或1。有一只蚂蚁从原点(0,0)出发,该蚂蚁只能在第一象限内上、下、左、右爬行,且每次爬行的长度为一个单位,即蚂蚁所在的位置(a,b)一定满足a、b为自然数。若f(x,y) = 0,则蚂蚁不可以到达点(x,y),否则蚂蚁可以到达该点。若给定任一点(m,n),有0<m<M,0<n<N,求解该蚂蚁是否能够到达该点的算法。
4.算法设计与分析
(1)分析
经过思考,发现从原点出发的蚂蚁(或山姆),要到达指定点(珠宝的位置),应该像走迷宫一样尝试所有可能,寻找出一条路径来为止。为说明方便,这里给每一个座标编号,只是为了说明方便,在实际的编码中,不会加入这里的编号,而是使用座标。如下图:
我们从左至右,从上至下依次编号。上图中原点(0,0)的编号为1;点(1,0)编号为2;点(2,0)编号为3;点(0,1)编号为7。
因此对于0<x<M,0<y<N的范围,点(x,y)编号为M*y+x+1。
对于上图,可以用如下的矩阵描述:
对于走迷宫,我们应该不断尝试新的可到达路径,对于每一个岔路都应该去尝试,直到到达指定出口,这里的珠宝位置就是出口;如果走进了死胡同,我们应该回溯,就是换一条路继续探索;另外还要防止死循环。综上,每到达一个岔路我们都应该标记一下,进入死循环或走进死胡同的时候回到岔路换一条路继续走。如下图:
这里的位置1就是一个岔路,因为有两条路可以走,即1->7或1->2。因此1位置要做一个标记。另外,为避免是循环,如1->2->8->7->1,此时又回到了1位置,因此1位置要做一个标记。
最后,我们每走一步,就在新位置上选择新的路径,那么我们的路径长度是一个增长的过程,并且增长的过程中还存在舍弃某些路径重新选取岔路的可能。所以,大体上,路径长度不断增加,新的点又可以不断增加进来或丢弃。因此,我们需要用“先进后出”的数据结构----栈!
(2)栈的push和pop操作
首先从原点出发,也就是编号为1的点('编号为N的点'简称为'点N')出发,1入栈,此时栈中只有点1,那么栈如下图所示:
然后发现点1是一个岔路,因为点1可以到达点7,也可以到达点2,那么1->7,1->2依次入栈。栈如下图所示
此时,我们到达了点2,发现点2可以连通点8和点3,于是2->8,2->3入栈,栈如下图所示。
然后我们到达了点3,3只能和点9连通不能和点4连通,于是3->9入栈。假设此时到了3发现是一个死胡同,即3不能和9连通,也不能和4连通。那么此时3就应该出栈,出栈后我们应该在路径1->2->8的基础上寻找出路。下图是假设3为死胡同时,3出栈后的新栈,如下图:
我们就这样反反复复地出栈入栈,直到我们到达指定的点,也就是目标点(此处为点31)出现在栈里面。或者最后发现栈为空,说明目标不可达。
5.编码
(1)设计迷宫栈
这里,我们使用C++语言来进行编码。我们需要一个栈,命名为LabyrinthStack。定义如下
stack<MyPoint *> LabyrinthStack;
可以用一个数组来保存路径内容,故栈中元素类型为一个个数组,数组中每个元素是一个点,故为MyPoint类型。
很容易知道数组最大长度为M*N,即迷宫中所有的点的个数。MyPoint类型定义为:
typedef struct myPoint
{
int x;//横座标
int y;//纵座标
int iFork;//该点是否为岔口
int nValue;//该点是否可以到达value=1表示可以,value=0表示不可以
int nflag;//该点是否已经入栈,1入栈;0未入栈。
int index;//索引,ID号,其中index = M*y+x+1,MxN表示迷宫矩阵大小
}MYPOINT,*pMYPOINT;
一个二维数组
(2)功能函数
我们需要一个函数,来找到指定点相互连通的函数,命名为IsAccess。
如下:
/*该函数判断该点是否可达
输入:x,点的横座标
y,点的纵座标
输出:不可达-1
可达:0
*/
int IsAccess(int x,int y)
{
if(x>=0 && x < M && y>=0 && y<N && grid[x][y] = 1)
{
return 0;
}
else
{
return -1;
}
}
(3)完整编码
以下C++代码在linux操作系统上G++未编译,没测试通过,功能也没通过(哈哈哈哈...)。
(未完,待续...)
#include <stack>
#include <iostream>
using namespace std;
typedef struct myPoint
{
int x;//横座标
int y;//纵座标
int iFork;//该点是否为岔口
int nValue;//该点是否可以到达value=1表示可以,value=0表示不可以
int nflag;//该点是否已经入栈,1入栈;0未入栈。
int index;//索引,ID号,其中index = M*y+x+1,MxN表示迷宫矩阵大小
}MYPOINT,*pMYPOINT;
int main(int argc, char *argv[])
{
stack<MYPOINT> LabyrinthStack;
int i = 0;
for(i=0;i<10;i++)
{
MYPOINT a;
a.x = i;
a.y = i;
a.iFork = 1;
a.index = i;
s.push(a);
}
cout<<"+=============================================+"<<endl;
while(!s.empty())
{
MYPOINT b = s.top();
s.pop();
}
return 0;
}
/*该函数判断该点是否可达
输入:x,点的横座标
y,点的纵座标
输出:不可达-1
可达:0
*/
int IsAccess(int x,int y, int M, int N)
{
if(x>=0 && x < M && y>=0 && y<N && grid[x][y] = 1)
{//既然可以到达,那就在这里入栈呗
return 0;
}
else
{
return -1;
}
}
bool isJewelry(int x,int y,int **grid)
{
if(grid[x][y] == 9)
{
return true;
}
return false;
}
int isPath(int rows, int columns, int **grid)
{//MxN表示迷宫矩阵大小,即rows x columns表示矩阵的大小
MYPOINT pointSet[rows*columns];//所有点。
int i = 0;
int j = 0;
int M = ROWS;
int N = columns;
//初始化所有点
for(i=0;i<M;++i)
{
for(j=0;j<N;++j)
{
pointSet[M*j+i+1].x = i;//横座标
pointSet[M*j+i+1].y = j;
pointSet[M*j+i+1].iFork = 0;
pointSet[M*j+i+1].nflag = 0;//此处所有点都没有入栈哦
pointSet[M*j+i+1].nValue = grid[i][j];
}
}
LabyrinthStack.push(pointSet[0]);//从原点出发
while(!LabyrinthStack.empty)
{
MYPOINT topElement = LabyrinthStack.top()
int x = topElement.x;//栈顶元素横座标 M*y+x+1
int y = topElement.y;//栈顶元素纵座标
int fork = 0;
if(!IsAccess(x+1,y))
{
if(isJewelry(x+1,y,grid))
{
return 1;//已经到达了珠宝的房间
}
LabyrinthStack.push(pointSet[M*y+x+1+1]);
}
if(!IsAccess(x,y+1))
{
if(isJewelry(x,y+1,grid))
{
return 1;//已经到达了珠宝的房间
}
LabyrinthStack.push(pointSet[M*(y+1)+x+1]);
}
if(!IsAccess(x-1,y))
{
if(isJewelry(x-1,y,grid))
{
return 1;//已经到达了珠宝的房间
}
LabyrinthStack.push(pointSet[M*y+x]);
}
if(!IsAccess(x,y-1))
{
if(isJewelry(x,y-1,grid))
{
return 1;//已经到达了珠宝的房间
}
LabyrinthStack.push(pointSet[M*(y-1)+x+1]);
}
if(fork == 0)
{//该点是死胡同,该点出栈
LabyrinthStack.pop(pointSet[M*y+x+1]);
}
}
return 0;//栈为空,山姆找不到通向珠宝房间的路线,返回0
}