迷宫求解.docx
《迷宫求解.docx》由会员分享,可在线阅读,更多相关《迷宫求解.docx(22页珍藏版)》请在冰豆网上搜索。
迷宫求解
一、题目
(一)课程设计的题目:
迷宫求解
说明:
求迷宫从入口到出口的路径,即从迷宫的入口出发,顺某一方向向前探索,若能走通,则继续往前走;否则沿原路退回,换一个方向继续探索,直到所有可能的通路都探索为止。
题目难度:
一般
设计要求:
给出迷宫的入口和出口及相关的通路,求出从入口到出口的路径。
要求使用C语言编程,定义合适的数据结构。
最后,需要说明设计思想,同时给出能够运行的源程序,并给出对应的程序流程图。
(二)对设计题目的理解:
根据对设计课题的分析,可以使用一个二维数组来表示迷宫,其中分别用1、0表示通与不通;算法的基本思想是:
若当前位置“可通”,则纳入“当前路径”,并继续朝“下一位置”探索,即切换“下一位置”为“当前位置”,如此重复,到达出口;若当前位置“不可通”,则应顺着“来向”退回到“前一通道块”,然后朝“来向”之外的其它方向探索。
若该通道块四周4个方块均“不可通”,则应从“当前路径”中删除该通道块。
使用栈结构记录当前路径,当前位置入栈表示向前行,出栈则表示从当前位置退回。
最后对路径进行检查,是否能够走得通并且可以走到最好的出口,如果可以就输出通过的路径,如果不可以就提示,没有结果。
二、设计思想
根据课题要求,首先是生成或建立一个迷宫,这个迷宫就是由一个二维数组来表示。
采用“穷举求解”的方法,即从入口出发,顺某一方向向前探索,若能走通,则继续向前走,否则沿原路退回,换一个方向再继续探索,直至所有可能的通路都探索到为止。
为保证在任何方向位置上都能沿原路退回,显然需要用一个后进先出的结构来保存从入口到当前位置的路径,因此就用到了栈。
首先如下图所示:
在DOS窗口下,用户可自行设置迷宫的大小,也可以选择由系统自动生成迷宫。
(1)选择一:
由电脑随机生成迷宫(8*8),图中黑色处代表通道,白色处为墙。
其坐标(1,1)为迷宫入口,坐标(8,8)为迷宫出口。
(a)|输入1,然后回车,系统进行迷宫寻找路径的全过程,其中“⊙”表示寻径过程的前进路径,“☆”表示寻径时,前方有障碍物,寻径回退路线。
(b)输入2,然后回车键,系统输出迷宫通路的坐标和探索迷宫所经过的全部路径。
(2)选择二:
手动输入迷宫的行数m:
8和列数n:
9,系统随机生成已设置好大小的迷宫,图中黑色处代表通道,白色处为墙。
其中点左上角(1,1)为迷宫入口,右下角(8,9)为迷宫出口。
(a)输入1,然后回车,系统进行迷宫寻找路径的全过程,其中“⊙”表示寻径过程的前进路径,“☆”表示寻径时,前方有障碍物,寻径回退路线。
(b)输入2,然后回车键,系统输出迷宫通路的坐标和探索迷宫所经过的全部路径。
假设“当前位置”指的是“在搜索过程中所在图中的某一位置”,则求迷宫中一条路径的算法的基本思想是:
若当前位置“可通”。
这纳入“当前路径”,并继续朝“下一路径”搜索,即切换“下一位置”为“当前位置”,如此重复直至到达出口。
若当前位置“不可通”,则应顺着“来向”退回到“前一位置”,然后再朝着除来向之外的其它方向继续探索;若该位置的4个方向均不可通,则从“当前路径”上删除该位置。
描述如下:
设定当前位置的初值为入口位置;
{若当前位置可通,
则{将当前位置插入栈顶;//纳入路径
若该位置是出口位置则结束;//求得路径存放在栈中
否则切换当前位置的右邻位置为新的当前位置;
}
否则,
若栈不空且栈顶位置尚有其他方向没有经过探索,
则设定新的位置为顺时针的栈顶位置的下一相邻块;
若栈不空但栈顶位置的四周均不通,
则{删去栈顶位置;//从路径中删去该通道块
若栈不空,则重新测试新的栈顶位置,
直到找到一个可通的相邻块或出栈到栈空;
}
三、程序源代码(包含关键代码的注释)
#include
#include
#include
#include
int**maze=0;
int**mark=0;
intm=0,n=0;
intmove[4][2]={{0,1},{1,0},{0,-1},{-1,0}};//移动的四个方向
typedefintElemType;
structStack//栈的定义
{
ElemType*stack;//创建栈元素类型
inttop;//栈高
intMaxsize;//栈最大的容量
};
//函数声名
voidInitStack(Stack&S);//初始化
voidPush(Stack&S,ElemTypeitem);//压栈
ElemTypePop(Stack&S);//出栈
ElemTypePeek(Stack&S);
boolEmptyStack(Stack&S);//判断栈是否为空
voidClearStack(Stack&S);
voidSeekPath();//找出出路
voidYanshi(Stack&S1,Stack&S2,Stack&S3,Stack&S4);
voidShuchu(Stack&S1,Stack&S2,Stack&S3,Stack&S4);//输出
//主函数
voidmain()
{
cout<<"------------------------------"<cout<<"欢迎来到迷宫求解"<cout<<"******************************"<cout<<"******************************"<cout<<"****"<cout<<"**1.随机生成迷宫**"<cout<<"**2.手动生成迷宫**"<cout<<"**3.退出迷宫世界**"<cout<<"****"<cout<<"******************************"<cout<<"******************************"<intch;inti,j;
cout<<"请输入所要执行的操作的序列号:
";
cin>>ch;
system("cls");//清屏
//随机产生迷宫
if(ch==1)
{
m=n=8;
intm_i=0;
maze=(int**)newint*[m+2];
for(m_i=0;m_imark=(int**)newint*[m+2];
for(m_i=0;m_isrand(time(0));//初始化随机数发生器
for(i=0;i{
for(j=0;j{
if((i==0)||(j==0)||(i==m+1)||(j==n+1))//迷宫的四面墙
{
maze[i][j]=1;
continue;
}
if(((i==1)&&(j==1))||((i==m)&&(j==n)))
{
maze[i][j]=0;
continue;
}
intt=rand()%4;
if((t==2)||(t==3))//随机设置通道
t=0;
maze[i][j]=t;
}
}
}
//自行输入迷宫
if(ch==2)
{
cout<<"迷宫的行数m:
";//输入迷宫的行数
cin>>m;
cout<<"迷宫的列数n:
";//输入迷宫的列数
cin>>n;
intm_i=0;
maze=(int**)newint*[m+2];
for(m_i=0;m_imark=(int**)newint*[m+2];
for(m_i=0;m_isrand(time(0));//初始化随机数发生器
for(i=0;i{
for(j=0;j{
if((i==0)||(j==0)||(i==m+1)||(j==n+1))//迷宫的四面墙
{
maze[i][j]=1;
continue;
}
if(((i==1)&&(j==1))||((i==m)&&(j==n)))
{
maze[i][j]=0;
continue;
}
intt=rand()%4;
if((t==2)||(t==3))//随机设置通道
t=0;
maze[i][j]=t;
}
}
}
if(ch==3)
exit
(1);
for(i=0;ifor(j=0;jmark[i][j]=0;//将每个点设成未经过
//输出初始的迷宫
cout<<"";
for(i=0;icout<
cout<for(i=0;i{
cout<
for(j=0;j{
if(maze[i][j]==0)
cout<<"□";//输出符号“□”,表示通路
if(maze[i][j]==1)
cout<<"■";//输出符号“■”,表示墙壁
}
cout<}
//找出出路
SeekPath();
}
voidInitStack(Stack&S)//初始化堆栈
{
S.Maxsize=10;
S.stack=newElemType[S.Maxsize];//申请动态空间
if(!
S.stack)//假如空间分配失败
{
cout<<"动态存储分配失败!
"<exit
(1);
}
S.top=-1;//栈顶指示
}
//压栈函数
voidPush(Stack&S,ElemTypeitem)//把元素压入堆栈
{
if(S.top==S.Maxsize-1)//若栈满,扩大两倍空间
{
intk=sizeof(ElemType);
S.stack=(ElemType*)realloc(S.stack,2*S.Maxsize*k);//继续申请空间
S.Maxsize=2*S.Maxsize;//扩大两倍空间
}
S.top++;
S.stack[S.top]=item;//新元素入栈
}
ElemTypePop(Stack&S)
{
if(S.top==-1)
{
cout<<"此时栈为空!
"<exit
(1);
}
S.top--;//栈顶指针退1,使其指向新的栈顶位置
returnS.stack[S.top+1];//返回栈顶元素值
}
//出栈函数
ElemTypePeek(Stack&S)
{
if(S.top==-1)
{
cout<<"此时栈为空!
"<exit
(1);
}
returnS.stack[S.top];
}
boolEmptyStack(Stack&S)
{
returnS.top==-1;
}
voidClearStack(Stack&S)
{
if(S.stack)
{
delete[]S.stack;
S.stack=0;
}
S.top=-1;
S.Maxsize=0;
}
//寻找出路函数
voidSeekPath()
{
StackS0,S1,S2,S3,S4,S5,S6,S7,S8,S9,S10,S11,S12;
InitStack(S0);//记录经过的坐标的转向
InitStack(S1);InitStack(S2);//反向记录经过通路的坐标
InitStack(S3);InitStack(S4);//反向记录所有经过的坐标
InitStack(S5);InitStack(S6);//正向记录通路的坐标
InitStack(S7);InitStack(S8);//正向记录所有经过的坐标
InitStack(S9);InitStack(S10);//反向记录退栈的坐标
InitStack(S11);InitStack(S12);//正向记录退栈的坐标
intx=1,y=1;
while((x!
=m)||(y!
=n))
{
if((x==1)&&(y==1)&&(maze[x][y]==0)&&(mark[x][y]==0))//(x,y)为可以到达点,将起入队
{
Push(S1,x);Push(S2,y);Push(S0,0);//加入坐标
mark[x][y]=1;//标记(x,y)已经到达过
Push(S3,x);Push(S4,y);//加入路径
}
intc;
for(intk=Peek(S0);k<4;k++)
{
c=Pop(S0);c++;Push(S0,c);
//根据当前点的状态确定下一个搜索点
intg,h;
g=x+move[k][0];
h=y+move[k][1];
if((maze[g][h]==0)&&(mark[g][h]==0))//(x,y)为可以到达点,将起入队
{
if(k==3)
{
Pop(S0);Push(S0,0);
}
mark[g][h]=1;//标记(g,h)已经到达过
Push(S1,g);Push(S2,h);Push(S0,0);//加入坐标
x=g;y=h;
Push(S3,g);Push(S4,h);//加入路径
break;
}
//k等于3时需要退栈
if(k==3)
{
Push(S9,Pop(S1));Push(S10,Pop(S2));//将S1,S1里的元素进栈到S9,S10
Pop(S0);
if(S1.top==-1)
{
//将S3,S4里的元素进栈到S7,S8
while(S3.top!
=-1)
{
Push(S7,Pop(S3));Push(S8,Pop(S4));
}
//将S9,S10里的元素进栈到S11,S12
while(S9.top!
=-1)
{
Push(S11,Pop(S9));Push(S12,Pop(S10));
}
cout<<"进行迷宫演示请输入1"<cout<<"输出迷宫通路请输入2"<cout<<"请输入所要执行的操作的序列号:
";
intg;
cin>>g;
//动态演示迷宫
if(g==1)
{
Yanshi(S7,S8,S11,S12);
}
//输出迷宫的通路坐标
if(g==2)
{
cout<<"此迷宫无通路!
"<}
intt;
cout<<"******************************"<cout<<"****"<cout<<"**返回迷宫世界请输入1**"<cout<<"**退出迷宫世界请输入2**"<cout<<"****"<cout<<"******************************"<cout<<"请输入所要执行的操作的序列号:
";cin>>t;
if(t==1)
{
Sleep(500);//窗口反应时间设为500毫秒
system("cls");//清屏
main();
}
if(t==2)
{
Sleep(500);
system("cls");//清屏
exit
(1);
}
}
x=Peek(S1);y=Peek(S2);//窥探s1,s2
Push(S3,x);Push(S4,y);//把(x,y)写入路径
break;
}
}
}
//将S1,S2里的元素进栈到S5,S6
while(S1.top!
=-1)
{
Push(S5,Pop(S1));Push(S6,Pop(S2));
}
//将S3,S4里的元素进栈到S7,S8
while(S3.top!
=-1)
{
Push(S7,Pop(S3));Push(S8,Pop(S4));
}
//将S9,S10里的元素进栈到S11,S12
while(S9.top!
=-1)
{
Push(S11,Pop(S9));Push(S12,Pop(S10));
}
cout<<"进行迷宫演示请输入1"<cout<<"输出迷宫通路请输入2"<cout<<"请输入所要执行的操作的序列号:
";
intg;
cin>>g;
//动态演示迷宫
if(g==1)
{
Yanshi(S7,S8,S11,S12);
}
//输出迷宫的通路坐标
if(g==2)
{
Shuchu(S5,S6,S7,S8);
}
intt;
cout<<"******************************"<cout<<"****"<cout<<"**返回迷宫世界请输入1**"<cout<<"**退出迷宫世界请输入2**"<cout<<"****"<cout<<"******************************"<cout<<"请输入所要执行的操作的序列号:
";cin>>t;
if(t==1)
{
Sleep(500);//窗口反应时间设为500毫秒
system("cls");//清屏
main();
}
if(t==2)
{
Sleep(500);//窗口反应时间设为500毫秒
system("cls");//清屏
exit
(1);
}
}
//动态演示迷宫函数
voidYanshi(Stack&S1,Stack&S2,Stack&S3,Stack&S4)
{
Sleep(500);
system("cls");
inti,j,k=0;
intt=S1.top;
while(k<=t)
{
system("cls");//清屏
into,p;
o=Pop(S1);
p=Pop(S2);
maze[o][p]=2;
cout<<"";
for(i=0;icout<
cout<for(i=0;i{
cout<
for(j=0;j{
if(maze[i][j]==2)
cout<<"⊙";//演示前进
if(maze[i][j]==3)
cout<<"☆";//演示后退
if(maze[i][j]==0)
cout<<"□";//表示通路
if(maze[i][j]==1)
cout<<"■";//表示墙壁
}
cout<}
if(S3.top!
=-1)
{
if(o==Peek(S3)&&p==Peek(S4))
{
maze[o][p]=3;
Pop(S3);Pop(S4);
}
}
k++;
Sleep(500);//窗口反应时间设为500毫秒
}
}
//输出迷宫通路坐标函数
voidShuchu(Stack&S1,Stack&S2,Stack&S3,Stack&S4)
{
cout<<"输出此迷宫的通路为:
"<while(S1.top!
=-1)
cout<<"("<cout<cout<<"输出所经过的全部路径为:
"<while(S3.top!
=-1)
cout<<"("<cout<}
四程序流程图
否
是
是
否
是
五设计总结
我本次课程设计的题目之所以选择迷宫求解是因为我一直就想着写个迷宫算法出来。
当初想迷宫算法肯定不难写,无穷求解算法、深度优先、出栈、入栈等工作,并且循环。
但是当真正写起来的时候才发现代码虽然不多,但写起来还是很复杂的,一个小小的程序用了我几天时间才写出来。
一个多星期写出了一个系统默认为8*8的迷宫,但中期检查时,老师说这个程过于简单,增加新的要求:
(1)可自行定义迷宫的大小,即可以手动输入迷宫的行数和列数;
(2)可以自行定义迷宫的出口和入口;(3)演示迷宫寻径过程和寻径时遇到墙壁时回退路径标记。
经过一翻探索,终于在原来的迷宫模块上境加了查以自定义迷宫大小和演示迷宫寻径过程和寻径时遇到墙壁时回退路径标记。
自行定义迷宫的出口和入口,用C语言写难度挺高的,我觉得用VB语言写起来可能会容易得多。
实验过程中的问题出现了很多,开始是语法错误,这个错误很好解决,编辑器可以提示错误的位置,可以迅速改掉语法错误。
程序死路的错误可是难上加难了。
由于最初开始写代码是心中的思路并不是很成熟,第一次的代码写到一半发现不正确不得不从新开始写。
后期调试的过程浪费了我大半部分精力,总是有些小的细节导致迷宫老是出现死循环状态。
针对死循环,调试时我采用限制循环次数的方式,并且每次循环都要将栈顶打印,当前坐在位置打印,这样可以方便看到程序是怎么运行的,以便及时发现程序中的细节性问题。
迷宫求解程序就是在不断调试,不断更改的过程中慢慢完成的。