1、实验内容
a问题描述:以一个m * n的长方阵表示迷宫,0和1分别表示迷宫的通路和障碍。设计一个程序,对任意设定的迷宫,求出一条从入口到出口的通路,或得出没有通路的结论。
b选做内容 (1)编写递归形式的算法,求得迷宫中所有的通路;
(2)一方阵型是输出迷宫及其通路。
c其本要求 :首先实现一个以链表做存储的栈类型,然后编写一个求解迷宫的非递归程序。求的通路以三元组(i,j,d)的形式输出,其中:(i,j)指示迷宫中的一个坐标,d表示走到下一坐标的方向。如:对于下列数据的迷宫,输出一条通路:(1,1,1),(1,2,2),(2,2,2),(3,2,3),(3,1,2)……。
d测试数据:迷宫的测试数据如下:左上角(1,1)为入口,右下角(8,9)为出口。
0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 |
0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 |
0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 |
0 | 1 | 1 | 1 | 0 | 0 | 1 | 0 |
0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 0 | 0 | 1 | 0 | 1 |
0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 |
1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 |
1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
f实现提示:计算机解迷宫通常用的是“穷举求解”方法,即从入口出发,顺着米一个方向进行探索,若能走通,则继续往前进;否则沿着原路退回,换一个方向继续探索,直至出口位置,求的一条通路。假如所有的可能的通路都探索到而未能到出口,则所设定的迷宫没有通路。可以二维数组存储迷宫数据,通常设定入口点的下标为(1,1),出口点的下标为(n,n)。为处理器方便起见,可在迷宫的四周加上一圈障碍 。对于迷宫中任一位置,均可约定有东、西、南、北四个方向可通。
2、概要设计
A设定栈的抽象数据类型定义:
ADT Stack{
数据对象:D={ai|ai∈CharSet,i=1,2,……,n,n>=0}
数据关系:R1={<ai-1,ai>| ai-1,ai∈D,i=2,……,n}
基本操作:
InitStack(&S)
操作结果:构造一个空栈S。
DestroyStack(&S)
初始条件:栈S已存在。
操作结果:销毁栈S。
ClearStack(&S)
初始条件:栈已存在。
操作结果:将S清为空栈。
StackLength(S)
初始条件:栈已存在。
操作结果:返回栈S的长度。
StackEmpty(S)
初始条件:栈已存在。
操作结果:若S为空栈则返回TRUE,否则返回FALSE。
GetTop(S,&e)
初始条件:栈已存在。
操作结果:若栈S不空,则以e返回栈顶元素。
Push(&S,e)
初始条件:栈已存在。
操作结果:在栈S的栈顶插入新的栈元素e。
Pop(&S,e)
初始条件:栈已存在。
操作结果:删除S的站定元素,并以e返回其值。
StackTracerse(S,visit())
初始条件:栈已存在。
操作结果:从栈低到栈顶依次对S中的每个元素调用函数visit()。
}ADT Stack
B设定迷宫的抽象数据类型为:
ADT maze{
数据对象:D={ai,j|ai,j∈{‘ ‘、‘#’、‘@’、‘*’},0<=i<=m+1,0<=j<=n+1,m,n<=0}
数绝关系:R={ROW,COL}
ROW={<Ai-1,j,ai,j>|ai-1,j,ai,j∈D,i=1,……,m+1,j=0,…,n+1}
COL={<ai,j-1,ai,j>|ai,,j-1,ai,j∈D,i=0,……,m+1,j=1,…,n+1}
基本操作:
InitMaze(&M,a,row,col)
初始条件:二维数组a[row+2][col+2]已存在,其中自第一行至第row+1行、每行中自第一列至第col+1列的元素已有值,并且以值,并且以值0表示通路,以值1表示障碍。
操作结果:构成迷宫的字符型数组,以空白字符表示通路,以字符‘#’表示障碍,并在迷宫四周加上一圈障碍。
MazePath(&M)
初始条件:迷宫M已被赋值。
操作结果:若迷宫M中存在一条通路,则按如下规定改变迷宫M的状态;以字符‘*’表示路径上的位置,字符‘@’表示‘死胡同’;否则迷宫的状态不变。
PrintMaze(M)
初始条件:迷宫M已存在。
操作结果:以字符形式输出迷宫。
}ADT maze;
C本程序包含三个模块:
a主程序模块:
void main(){
初始化;
Do{
接受命令;
处理命令;
}while(命令!=”退出”)
}
b站模块——实现抽象数据类型
c迷宫模块——实现迷宫抽象数据类型
个模块之间的调用关系如下:
主程序
↓
迷宫模块
↓
栈模块
D求解迷宫一条通路的伪码算法:
设定当前位置的初值为入口位置;
Do{
若当前位置可同,
则{
将当前位置插入栈顶;
若该位置上级出口位置,则结束;
否则切换当前位置的东邻方块为新的当前位置;
}
否则{
若栈不空且栈顶位置尚有其他方向未被探索。
则设定新的当前位置为沿顺时针方向旋转找到的栈顶位置的下一相邻块;
若栈不空但栈顶位置的四周均不可通,
则{
删去栈顶位置;
若栈不空,则重新测试新的栈顶位置,
直至找到一个可通的相邻块或出栈至栈空。
}
}
}while(栈不空)
{栈空说明没有路仅存在}
3、详细设计
A坐标类型:
typedef struct{
int r,c;
} PosType;//迷宫中r行c列的位置
B迷宫类型:maze.h文件
typedef struct MazeType{
int r;
int c;
char arr[MAXLEN][MAXLEN];//可取' ''*' '@' '#'
}MazeType;//MazeType
Status MarkPrint(MazeType &maze,PosType curpos){
maze.arr[curpos.r][curpos.c]='@';//"@"表示曾走过但不通
return OK;
}//Markprint曾走过但不是通路标记并返回OK
Status FootPrint(MazeType &maze,PosType curpos){
maze.arr[curpos.r][curpos.c]='*';//"*"表示可通
return OK;
}//FootPrint
PosType NextPos(PosType &curpos,int i){
PosType cpos;
cpos=curpos;
switch(i){ //1.2.3.4分别表示东,南,西,北方向
case 1 : cpos.c+=1; break;
case 2 : cpos.r+=1; break;
case 3 : cpos.c-=1; break;
case 4 : cpos.r-=1; break;
default: exit(ERROR);
}//switch
return cpos;
}//Nextpos
Status Pass(MazeType &maze, PosType curpos){
/*for(int i=0;i<10;i++){
for(int j=0;j<10;j++){
printf("%4c",maze.arr[i][j]);
}
printf("\n");
}*/
//char a=' ';
//printf("%4d",a);
//printf("%d,%d",curpos.r,curpos.c);
//printf("%4c",maze.arr[1][1]);
if(maze.arr[curpos.r][curpos.c]==' '){//当前位置可通
//printf("go");
return TRUE;
}//if
else{
//printf("go");
return FALSE;
}//else
}//Pass
void InitMaze(MazeType &maze, char a[MAXLEN][MAXLEN], int row, int col){
//printf("go");
maze.r=row;
maze.c=col;
//printf("%d,%d",row,col);
for(int i=0;i<=col+1;i++){a[0][i]='1';a[row+1][i]='1';}
//printf("go");
for(i=0;i<=row+1;i++){a[i][0]='1';a[i][col+1]='1';}
//printf("go");
/*for(i=0;i<10;i++){
for(int j=0;j<10;j++){
printf("%4c",a[i][j]);
}
printf("\n");
}
*/
for(i=0;i<=maze.r+2;i++){
for(int j=0;j<maze.c+2;j++){
if(a[i][j]=='1') maze.arr[i][j]='#';
//if(a[i][j]==0) maze.arr[i][j]=' ';
else maze.arr[i][j]=' ';
}//for
}//for
}//InitMaze
Status MazePath(MazeType &maze,PosType start,PosType end)
{//求解迷宫maze中,从入口start到出口end的一条路径,
//若存在则返回TRUE,否则返回FALSE
PosType curpos;
int curstep;
SqStack S;
SElemType e;
InitStack(S);
curpos=start;//设定“当前位置”为“入口位置”
curstep=1; //探索第一步
found=false;
do{
if(Pass(maze,curpos))
{
//当前位置可以通过,即是未曾走到过的通道块留下足迹
FootPrint(maze,curpos);//做可以通过的标识
e.step=curstep;
e.seat=curpos;
e.di=1;//为栈顶元素赋值
if(Push(S,e)!=OK) return ERROR;
if(curpos.r==end.r && curpos.c==end.c) found=true;//如果到达终点返回true
else{
curpos=NextPos(curpos,1);//下一位置是当前位置的东邻
curstep++;//探索下一步
}//else
}//if
else
if(StackEmpty(S)!=1)
{
Pop(S,e);
while(e.di==4 && !StackEmpty(S))
{
MarkPrint(maze,e.seat);
Pop(S,e);
curstep--;
}//while
if(e.di<4)
{
e.di++;
Push(S,e);//换下个方向探索
curpos=NextPos(e.seat,e.di);
}//if
}//if
}while(StackEmpty(S)!=1&&!found);
DestroyStack(S);
if(found)
return true;
else
return false;//没有找到路径
}//MazePath
void PrintMaze(MazeType &maze){
//将标记路径信息的迷宫输出到终端(包括外墙)
for(int i=0;i<=maze.r+2;i++){
for(int j=0;j<=maze.c+2;j++){
printf("%4c",maze.arr[i][j]);//输出迷宫
}//for
printf("\n");
}//for
}//PrintMaze
C栈类型:stkpas.h
typedef struct{
int step; // 当前位置在路径上的“序号”
PosType seat; //当前的坐标位置
int di; //往下一坐标位置的方向
}SElemType;
typedef struct NodeType{
SElemType data;
NodeType *next;
}NodeType,*LinkType;//结点类型
typedef struct SqStack{
LinkType top;
int stacksize;
}SqStack; //栈类型
Status InitStack(SqStack &S){
S.top=(LinkType)malloc(sizeof(NodeType));
S.top->next=NULL;
S.stacksize=0;
return OK;
}//InitStack;
Status Push(SqStack &S,SElemType &e){
LinkType p;
p=(NodeType*)malloc(sizeof(NodeType));
if(!p) return ERROR;
p->data=e;
p->next=S.top;
S.top=p;
S.stacksize++;
//S.top=S.base+S.stacksize;
//S.top->data=e;
return OK;
}//Push
Status StackEmpty(SqStack S){
if(S.top->next==NULL) return OK;
return ERROR;
}//StackEmpty
Status Pop(SqStack &S,SElemType &e){
LinkType p;
if(StackEmpty(S)) return ERROR;
p=S.top;
e=p->data;
S.top=S.top->next;
S.stacksize--;
free(p);
return OK;
// 栈顶元素出栈
}//Pop
Status DestroyStack(SqStack &S){//销毁栈S,
LinkType p;
while(S.top!=NULL){
p=S.top;
S.top=S.top->next;
free(p);
}//while
if(S.top==NULL) return OK;
else return ERROR;
}//DestroyStack
D主函数:testmaze.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "base.h"
#include "stkpas.h"
bool found=false;
#include "maze.h"
PosType start;
PosType end;
MazeType maze;
void Initialization(){
system("cls");
printf("********************************************************");
printf("\n*CreatMaze--c MazePath--m PrintMaze--p Quit--q*");
printf("\n********************************************************");
}//Initialization
void ReadCommand(char &cmd){
do{
if(cmd=='c'||cmd=='C'){
printf("\n********************************************************");
printf("\n*Enter a operation :m或M *");
printf("\n*退出--Enter a operation :q或Q *");
printf("\n********************************************************");
}//if
else if(cmd=='m'||cmd=='M'){
printf("\n********************************************************");
printf("\n*Enter a operation :p或P *");
printf("\n*退出--Enter a operation :q或Q *");
printf("\n********************************************************");
}//else if
else if(cmd=='0'){
printf("\n********************************************************");
printf("\n*Enter a operation :c或C *");
printf("\n*退出--Enter a operation :q或Q *");
printf("\n********************************************************");
}
printf("\n\n Operration:-");
cmd=getchar();
}while(!(cmd=='c'||cmd=='C'||cmd=='m'||cmd=='M'||cmd=='p'||cmd=='P'||cmd=='q'||cmd=='Q'));
}//ReadCommand
void Interpre(char cmd){
//MazeType maze;//开始的时候定义在这个地方,不是全局的,调用出现了问题。
//PosType start;//开始的时候定义在这个地方,不是全局的,调用出现了问题。
//PosType end;//开始的时候定义在这个地方,不是全局的,调用出现了问题。
switch(cmd){
case 'C':
case 'c':{
int rnum, cnum, i=0,m=1,n=1;
char a2[MAXLEN][MAXLEN];
char input[1];
char data[100000];
printf("\n请输入迷宫数据文件名!\n");
//printf("go");
scanf("%s",input);
FILE *fp;
fp=fopen(input,"r");
//printf("go");
if(!fp){
printf("\n不能打开文件\n");
break;
}//if
//printf("go");
while(!feof(fp)){
fscanf(fp,"%s",&data[i]);
if(i==0){rnum=(int)data[i]-(int)'0';/*printf("%d",rnum);*/}
if(i==1){cnum=(int)data[i]-(int)'0';/*printf("%d",rnum);*/}
if(i>=2){
if(n>cnum){m++;n=1;}
a2[m][n]=data[i];
//printf("%d",a2[m][n]);
n++;
}//if
i++;
}//while
fclose(fp);
//printf("go");
InitMaze(maze, a2, rnum, cnum);
printf("\n迷宫建立完成!!\n");
//PrintMaze(maze);
break;
}//case
case 'M':
case 'm':{
printf("\n请输入迷宫入口的坐标,以空格为间隔:--");
scanf("%d %d",&start.r,&start.c);
printf("\n请输入迷宫出口的坐标,以空格为间隔:--");
scanf("%d %d",&end.r,&end.c);
//printf("%d,%d,%d,%d",start.r,start.c,end.r,end.c);
//PrintMaze(maze);
MazePath(maze, start, end);
//PrintMaze(maze);
break;
}//case
case 'P':
case 'p':{
if(found){
printf("\n求解迷宫的结果如下--\n");
PrintMaze(maze);
}//if
else{
printf("\n找不到路径!\n");
}//else
}//case
}//switch
}//Interpre
void main(){
char cmd='0';
Initialization();
//cmd=getchar();
do{
ReadCommand(cmd);
Interpre(cmd);
}while(cmd!='q'&&cmd!='Q');
}//main
E主调用:
#define MAXLEN 10//迷宫包括外墙最大行列数目
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status;
#define STACK_INIT_SIZE 100 //存储空间初始分配量
typedef struct{
int r,c;
} PosType;//迷宫中r行c列的位置
4、测试数据及程序运行情况
使用了四个文件进行测试
分别是a.txt,b.txt,c.txt,d.txt
b.txt文件数据为
3 2
0 0
0 0
0 0
出口位置:1 1
入口位置:3 2
求解出的迷宫为
# | # | # | # |
# | * | * | # |
# |
| * | # |
# |
| * | # |
# | # | # | # |
其他不一一列举!
5、实验过程中出现的问题及解决方法
A读取文件方面不容易,必须控制好每次读取的内容,这样才能将读取的数据划分开,哪些是行数和列数,哪些是迷宫地图。不同的函数每次对文件的读取也不同,必须弄明白和清楚函数具体每次读多少,读到空格停不停,读到换行又如何。。。。。。
具体解决: int rnum, cnum, i=0,m=1,n=1;
char a2[MAXLEN][MAXLEN];
char input[1];
char data[100000];
printf("\n请输入迷宫数据文件名!\n");
//printf("go");
scanf("%s",input);
FILE *fp;
fp=fopen(input,"r");
//printf("go");
if(!fp){
printf("\n不能打开文件\n");
break;
}
//printf("go");
while(!feof(fp)){
fscanf(fp,"%s",&data[i]);
if(i==0){rnum=(int)data[i]-(int)'0';/*printf("%d",rnum);*/}
if(i==1){cnum=(int)data[i]-(int)'0';/*printf("%d",rnum);*/}
if(i>=2){
if(n>cnum){m++;n=1;}
a2[m][n]=data[i];
//printf("%d",a2[m][n]);
n++;
}
i++;
}
//printf("go");
fclose(fp);
B数组的声明不可太大,绝对不可太小,否则经常出项内存不能读的错误。太大也同会出现同样的问题。
C数组间的传值,把一个数组的数据挪到maze中的时候,注意判断的范围,因为中间涉及了一些行和列,一定弄清楚,否则会出现迷宫变形,不是自己期望的样子。
D不同的头文件之间的调用的问题,弄清楚头文件之间的调用关系,不可在所有的头文件中都加上其他的头文件,因为这样会产生调用深度的错误。
具体解决:头文件的调用只能像树一样有顺序的调用,不可出现环。
被调用者要放在调用者前边。否则会出现未声明变量和重复定义的错误。
6、实验体会
长时间不碰一件东西,很快就会把其忘记了,就像这次课程设计,刚接到程序的时候,只是有个大概印象,具体起来发现以前学的忘记的太多,链栈基本忘干净了,在几天的程序里把其复习了,加深了在脑中的印象。
在几天的程序里,有些东西学的深了,就像上面列出的几个在实验中遇到的问题。在解决问题的同时学到了这些东西。