迷宫问题
1设计目的、要求
以一个m*n的长方阵表示迷宫,0和1分别表示迷宫中的通路和障碍。设计一个程序,对任意设定的迷宫,求出一条从入口到出口的通路,或得出没有通路的结论。
基本要求:
首先实现一个以链表作存储结构的栈类型,然后编写一个求解迷宫的非递归程序。求得的通路以三元组(i,j,d)的形式输出,其中:(i,j)指示迷宫中的一个坐标,d表示走到下一坐标的方向,如:对于下列数据的迷宫,输出的一条通路为:(1,1,1),(1,2,2),(3,2,3),(3,1,2),…。
(1)编写递归形式的算法,求得迷宫中所有可能的通路;
(2)以方阵形式输出迷宫及其通路。
2设计原理
主要采取三大模块:主程序模块、栈模块和迷宫模块
栈模块:实现迷宫数据的抽象化和对迷宫数据的处理;
迷宫模块:实现迷宫数据抽象类型;
主程序模块:初始化迷宫模块。
3采用软件、设备
Microsoft Visual C++ 6.0 微型电子计算机
4设计内容
1、迷宫模块:
以二维数组Maze[m+2][n+2]表示迷宫,其中:Maze[0][j]和Maze[m+1][j](0<=j<=n+1)及Maze[i][0]和Maze[i][n+1] (0<=i<=n+1)为添加的一圈障碍。数组中一元素值为0表示通路,1表示障碍,限定迷宫的大小m,n<=10。其中迷宫的入口位置和出口位置可由用户随时设定。
1.坐标位置类型:
struct PosType /* 迷宫坐标位置类型 */
{
int x; /* 行值 */
int y; /* 列值 */
};
2.迷宫类型:
#define MAXLENGTH 25 /* 设迷宫的最大行列为25 */
typedef int MazeType[MAXLENGTH][MAXLENGTH];
3.记录坐标的三个一元数组:
int a[MAXLENGTH];
int b[MAXLENGTH];
int c[MAXLENGTH];
2、栈模块:
1.栈类型SElemType:
struct SElemType/* 栈的元素类型 */
{
int ord; /* 通道块在路径上的"序号" */
PosType seat; /* 通道块在迷宫中的"坐标位置" */
int di; /* 从此通道块走向下一通道块的"方向"(0~3表示东~北) */
};
2.构造一个栈SElmType:
struct SqStack //SqStack
{
SElemType *base; /* 在栈构造之前和销毁之后,base的值为NULL */
SElemType *top; /* 栈顶指针 */
int stacksize; /* 当前已分配的存储空间,以元素为单位 */
}; /* 顺序栈 */
其中基本操作如下:
栈的初始化:
bool InitStack(SqStack *S)
{ /* 构造一个空栈S */
(*S).base=(SElemType *)malloc(STACK_INIT_SIZE*sizeof(SElemType));
if(!(*S).base)
exit(1); /* 存储分配失败 */
(*S).top=(*S).base;
(*S).stacksize=STACK_INIT_SIZE;
return true;
}
元素进栈:
bool Push(SqStack *S,SElemType e)
{ /* 插入元素e为新的栈顶元素 */
if((*S).top-(*S).base>=(*S).stacksize) /* 栈满,追加存储空间 */
{
(*S).base=(SElemType *)realloc((*S).base,((*S).stacksize+STACKINCREMENT)*sizeof(SElemType));
if(!(*S).base)
exit(1); /* 存储分配失败 */
(*S).top=(*S).base+(*S).stacksize;
(*S).stacksize+=STACKINCREMENT;
}
*((*S).top)++=e;
return true;
}
判断栈是否为空:
bool StackEmpty(SqStack S)
{ /* 若栈S为空栈,则返回true,否则返回false*/
if(S.top==S.base)
return true;
else
return false;
}
删除栈顶元素使之为空:
bool Pop(SqStack *S,SElemType *e)
{ /* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回true;否则返回false */
if((*S).top==(*S).base)
return false;
*e=*--(*S).top;
return true;
}
寻找公共路径的思想图如下:
设定当前为出始值的入口:
do{
若当前位置可通,
则{将当前位置插入栈顶;
若该位置是出口位置,则结束;
否则切换当前位置的东邻方块为新的当前位置;
}
否则{
若栈不空且栈顶位置尚有其他方向未被探索,
则设定新的当前位置为沿顺时针方向旋转找到的栈顶位置的下一邻块;
若栈不为空且栈顶位置的四周均不可通,
则{删除栈顶位置;
若栈不为空,则重新测试新的栈顶位置,
直至找到一个可通的相邻块或出栈至栈空;
}
}
bool MazePath(PosType start,PosType end) /* 算法3.3 */
{ /* 若迷宫maze中存在从入口start到出口end的通道,则求得一条 */
/* 存放在栈中(从栈底到栈顶),并返回TRUE;否则返回FALSE */
SqStack S;
PosType curpos;
SElemType e;
InitStack(&S);
curpos=start;
do
{
if(Pass(curpos))
{ /* 当前位置可以通过,即是未曾走到过的通道块 */
FootPrint(curpos); /* 留下足迹 */
e.ord=curstep;
a[curstep]=e.seat.x=curpos.x;
b[curstep]=e.seat.y=curpos.y;
c[curstep]=e.di=0;
Push(&S,e); /* 入栈当前位置及状态 */
curstep++; /* 足迹加1 */
if(curpos.x==end.x&&curpos.y==end.y) /* 到达终点(出口) */
return true;
curpos=NextPos(curpos,e.di);
}
else
{ /* 当前位置不能通过 */
if(!StackEmpty(S))
{
Pop(&S,&e); /* 退栈到前一位置 */
curstep--;
while((e.di==3) && (!StackEmpty(S))) /* 前一位置处于最后一个方向(北) */
{
MarkPrint(e.seat); /* 留下不能通过的标记(-1) */
Pop(&S,&e); /* 退回一步 */
curstep--;
}
if(e.di<3) /* 没到最后一个方向(北) */
{
e.di++; /* 换下一个方向探索 */
Push(&S,e);
a[curstep]=e.seat.x=curpos.x;
b[curstep]=e.seat.y=curpos.y;
c[curstep]=e.di;
curstep++;
curpos=NextPos(e.seat,e.di); /* 设定当前位置是该新方向上的相邻块 */
}
}
}
}while(!StackEmpty(S));
return false;
}
3、主程序模块:
Void main()
{
Do
{
接受命令;
处理命令;
}while(命令!=“退出”)
}
程序如下:
void main()
{
PosType begin,end;
int i,j,x,y,x1,y1;
cout<<"请输入迷宫的行数,列数(包括外墙):";
cin>>x>>y;
for(i=0;i<x;i++) /* 定义周边值为1(同墙) */
{
m[0][i]=1; /* 行周边 */
m[x-1][i]=1;
}
for(j=1;j<y-1;j++)
{
m[j][0]=1; /* 列周边 */
m[j][y-1]=1;
}
for(i=1;i<x-1;i++)
for(j=1;j<y-1;j++)
m[i][j]=0; /* 定义通道初值为0 */
cout<<"请输入迷宫内墙单元数:";
cin>>j;
cout<<"请依次输入迷宫内墙每个单元的行数,列数:"<<endl;
for(i=1;i<=j;i++)
{
cin>>x1>>y1;
m[x1][y1]=1; /* 定义墙的值为1 */
}
cout<<"迷宫结构如下:"<<endl;
Print(x,y);
cout<<"请输入起点的行数,列数:";
cin>>begin.x>>begin.y;
cout<<"请输入终点的行数,列数:";
cin>>end.x>>end.y;
if(MazePath(begin,end)) /* 求得一条通路 */
{
cout<<"此迷宫从入口到出口的一条路径如下:"<<endl;
Print(x,y); /* 输出此通路 */
cout<<"迷宫所走过路径的坐标及方向(0代表右,1代表下,2代表左,3代表上):"<<endl;
for(i=1;i<curstep;i++)
cout<<"("<<a[i]<<","<<b[i]<<","<<c[i]<<")"<<endl;
}
else
cout<<"此迷宫没有从入口到出口的路径"<<endl;
}
5原始程序、数据
输入迷宫的行数和列数8,8;输入迷宫中障碍数17个;输入障碍物所在的坐标(1,2),(1,3),(1,4),(1,5),(1,6),(3,2),(3,3),(3,4),(3,5),(3,6)(4,4),(5,1),(5,2),(5,5),(6,1),(6,2),(6,3)。设置起点和终点分别为(1,1)和(6,6)。得到最终走出的路径和其走过路径的坐标及方向。
6结果分析
7参考文献
《数据结构实用教程》 清华大学出版社
《数据结构习题集》 清华大学出版社
8附录:
/*存在的问题,没有找到所有通路
例如
0 0 1 0
0 1 0 1
0 0 1 0
1 0 0 0
*/
#include<iostream>
#include<time.h>
using namespace std;
const int MaxX = 50; //最大列数
const int MaxY = 50; //最大行数
struct Point //点
{
int x;
int y;
};
class CLabyrinth
{
int X,Y; //迷宫的大小
int Map[MaxX][MaxY]; //迷宫结构
Point Result[255]; //存放正确的路径(栈)
int top; //栈顶(指针)
public:
CLabyrinth(); //得到迷宫尺寸
void SetMap(); //手工设置迷宫结构
void AutoSetMap(); //自动设置迷宫结构
void ShowMap(); //显示迷宫
void SearchWay(); //搜索通路
void ShowResult(); //显示通路
};
CLabyrinth::CLabyrinth()
{
cout<<"\t\t ★欢迎进入迷宫★\t\t\n"
<<" 输入迷宫的大小X*Y(最大为50*50):\n"
<<"【不建议超过8列,因为绘出的地图将不会显示】\n";
int x,y;
cout<<" 请输入行数:"<<endl;
cin>>x;
cout<<" 请输入列数:"<<endl;
cin>>y;
X = (x>48) ? 50 : x+2; //多两个单位是存放迷宫外壁
Y = (y>48) ? 50 : y+2;
top=-1;
}
void CLabyrinth::SetMap()
{
cout<<"\n依次输入迷宫的结构(1为有障碍,0为无障碍):\n"
<<"前进方向有4个,分别是上、下、左、右\n";
for(int i=0,j,t;i<X;i++)
for(j=0;j<Y;j++)
{
if(i==0 || j==0 || i==X-1 || j==Y-1) //在迷宫周围包上一圈1,防止搜索通路时越界
{
Map[i][j]=1;
continue;
}
cin>>t;
Map[i][j] = (t!=0) ? 1 : 0;
}
}
void CLabyrinth::AutoSetMap()
{
cout<<"\n前进方向有4个,分别是上、下、左、右、\n";
int a(39),b(72);
for(int i=0,j;i<X;i++)
{
srand((unsigned)time(NULL));
for(j=0;j<Y;j++)
{
if(i==0 || j==0 || i==X-1 || j==Y-1)
{
Map[i][j]=1;
continue;
}
srand((rand()*(a++)+(b++))%65536);
Map[i][j]=rand()%2;
}
}
}
void CLabyrinth::ShowMap()
{
if(Y>10) return;
cout<<"\n迷宫示意图为:\n";
for(int i=1,j;i<X-1;i++)
{
for(j=1;j<Y-1;j++)
{
if(i==1&&j==1) //cout<<"\n\t入口->";
cout<<"入口-> ";
else cout<<"\t ";
cout<<Map[i][j];
}
if(i==X-2&&j==Y-1) cout<<" ->出口";
cout<<endl;
}
}
void CLabyrinth::SearchWay()
{
if(Map[X-2][Y-2]) return; //如果出口不通直接返回
int i(X-2),j(Y-2); //出口的坐标 从出口向入口找路
int pow; //检查方向的计数器
do
{
if(!Map[i][j])
{
Result[++top].x = i; //将要可通行的坐标入栈
Result[top].y = j;
if(Result[top].x==1&&Result[top].y==1) break; //如果找到了入口退出循环
i = i-1; //下一个要检查的坐标 左斜上方
j = j-1;
pow=0; //计数器清零
}
else
{
pow++;
i = Result[top].x; //取出栈顶元素 上一次的可通行坐标
j = Result[top].y;
switch(pow)
{
case 1: i--; break; //上
case 2: j--; break; //左
case 3: i--;j++;
if(i==Result[top-1].x && j==Result[top-1].y) //若是来路
pow++;
else break;
case 4: i++;j--;
if(i==Result[top-1].x && j==Result[top-1].y)
pow++;
else break;
default: Map[i][j] = 1;i = Result[--top].x;j = Result[top].y; //将该坐标设为不可通行 再回到上一次可通行坐标
}
}
}while(top!=-1); //栈不为空继续循环
}
void CLabyrinth::ShowResult()
{
if(top==-1)
{
cout<<"\n此迷宫没有通路!!!\n";
return;
}
cout<<"\n此迷宫的其中一条通路为:\n入口";
for(;top!=-1;top--)
cout<<"->("<<Result[top].x<<','<<Result[top].y<<')';
cout<<"->出口\n";
}
void main()
{
CLabyrinth lab;
while(1)
{
cout<<"请选择下列操作:\n";
cout<<"\n1、手工绘制迷宫\n"
<<"2、电脑绘制迷宫(很大几率绘制不出有通路地图)\n";
int usr;
cin>>usr;
if(usr==1) lab.SetMap();
else lab.AutoSetMap();
lab.ShowMap();
lab.SearchWay();
lab.ShowResult();
cout<<"再来一次(Y/N)?";
char again;
cin>>again;
if(again!='Y'&&again!='y') break;
}
}
/*存在的问题,没有找到所有通路
例如
0 0 1 0
0 1 0 1
0 0 1 0
1 0 0 0
*/