maze[i][j]=rand()%2;//随机生成0、1
maze[0][0]=0;//将开始和结束位置强制为0,保证有可能出来迷宫
maze[m-1][n-1]=0;
}
2、迷宫路径的搜索
在生成的0、1矩阵迷宫中,首先从迷宫的入口开始,如果该位置就是迷宫出口,则已经找到了一条路径,搜索工作结束。
否则搜索其北(-1,0),东北(-1,1),东(0,1),东南(1,1),南(1,0),西南(1,-1),西(0,-1),西北(-1,-1)8个方向位,是否是障碍,若不是障碍,就移动到该位置,然后再从该位置开始搜索通往出口的路径;若是障碍就选择另一个相邻的位置,并从它开始搜索路径。
为防止搜索重复出现,则将已搜索过的位置标记为2,同时保留搜索痕迹,在考虑进入下一个位置搜索之前,将当前位置保存在一个队列中,如果所有相邻的非障碍位置均被搜索过,且未找到通往出口的路径,则表明不存在从入口到出口的路径。
这实现的是广度优先遍历的算法,如果找到路径,则为最短路径。
逆序输出路径,将已输出的路径标记为3。
实验数据如下:
表3.1方向move的偏移量
Name
dir
Move[dir].vert
Move[dir].horiz
N
0
-1
0
NE
1
-1
1
E
2
0
1
SE
3
1
1
S
4
1
0
SW
5
1
-1
W
6
0
-1
NW
6
0
-1
3测试结果
图1
图2
图3
图4
图5
图6
图7
4分析与探讨
首先明确题目中的已知条件:
(1)迷宫是一个8*8大小的矩阵。
(2)从迷宫的左上角进入,右下角为迷宫的终点。
(3)maze[i][j]=0代表第i+1行第j+1列的点是通路;maze[i][j]=1代表该点是墙,无法通行。
(4)迷宫有两种生成方式:
手工设定和自动生成。
(5)当老鼠处于迷宫中某一点的位置上,它可以向8个方向前进,分别是:
“上、下、左、右、左上、左下、右上、右下”8个方向。
(6)要实现这个程序,首先要考虑如何表示这个迷宫。
在实例程序中使用二维数组maze[N+2][N+2]来表示这个迷宫,其中N为迷宫的行、列数。
当值为“0”时表示该点是通路,当值为“1”时表示该点是墙。
老鼠在迷宫的位置在任何时候都可以由行号row和列号cool表示。
(7)为什么指定:
maze[N+2][N+2]来表示迷宫,而不是使用maze[N][N]来表示迷宫?
原因是当老鼠跑到了迷宫的边界点时就有可能跳出迷宫,而使用maze[N+2][N+2]就可以把迷宫的外边再包一层“1”,这样就能阻止老鼠走出格。
(8)老鼠在每一点都有8种方向可以走,分别是:
North,NorthEast,East,SouthEast,
South,SouthWest,West,NorthWest。
可以用数组move[8]来表示每一个方向上的横纵坐标的偏移量,见表3.1。
根据这个数组,就很容易计算出沿某个方向行走后的下一个点的坐标。
方向move的偏移量
Name
dir
Move[dir].vert
Move[dir].horiz
N
0
-1
0
NE
1
-1
1
E
2
0
1
SE
3
1
1
S
4
1
0
SW
5
1
-1
W
6
0
-1
NW
6
0
-1
迷宫问题可以用深度优先搜索方法实现。
当老鼠在迷宫中移动的时候,可能会有许多种移动选择方向。
程序需要记录并用栈来保存当前点的坐标,然后任意选择一个方向进行移动。
由于应用栈保存了当前通道上各点的坐标,所以当在当前各方向上都走不通时可以返回上一个点,然后选择另一个方向前进。
可定义element结构用于存储每一步的横纵坐标和方向。
typedefstruct{
shortintrow;
shortintcol;
shortintdir;
}element;
Elementstack[MAX_STACK_SIZE];
根据表3.1可推算出每次移动后的坐标。
设当前的坐标是(row,col),移动的方向是dir,移动后的点是next,则有
next_row=row+move[dir].vert;
next_col=col+move[dir].horiz;
可用另一个二维数组mark[N+2]来记录哪些点已经被访问过。
当经过点maze[row][col]时,相应地将mark[row][col]的值从0置为1。
本程序支持自动生成迷宫。
利用random
(2)函数可随机产生0或1,来支持迷宫的自动生成。
注意maze[N][N]和maze[1][1]一定要等于0,因为他们分别是起点和终点。
如果找到了一条走出迷宫的路径,则需要在屏幕中打印出如图3.5所示格式的信息。
这里要用到graphics.h即TC中的图形库(注意:
本程序是TC上的实现,而VC++有自己的图形库,所以使用VC++编译提示错误)。
针对图3.5,可使用circle()函数画圆,outtexttxy()函数标记文字,并用line()函数来划线。
程序的主要函数如下:
●函数voidadd(int*top,elementitem),将当前步的信息item压入到作为全局变量的栈stack(栈顶为top)中。
●函数elementdelete(int*top),返回stack中栈顶的元素。
●函数voidpath(void),采用深度优先搜索算法,首先取出栈顶元素作为当前点选择一个方向前进到下一个点(如果能走得话);然后,将下一个点压入栈,并将二维数组mark中对应的值改为1,表示该点已经走到了。
反复执行上面两步,当走到一个点不能再走下去了(已经尝试了各个方向并失败),并且这个点不是终点,则这个点的上一个点会从栈中被跑抛出,从而“回朔”到上一点;当遇到终点时,程序结束,找到一条路径;当在程序循环过程中遇到栈为空,则说明该迷宫根本无法走到终点。
5小结
为期一个星期的数据结构课程设计快结束了,这使我对深度和广度优先搜索有了更加深刻的理解和认识。
我们团队负责的迷宫问题的课程设计就是充分的利用深度和广度优先搜索的有关知识,主要运用的是广度优先搜索遍历。
(1)深度优先搜索遍历:
深度优先搜索是一个递归过程。
首先访问一个顶点Vi并将其标记为已访问过,然后从Vi的任意一个未被访问的邻接点出发进行深度优先搜索遍历。
如此执行,当Vi的所有邻接点均被访问过时,则退回到上一个顶点Vk,从Vk的另一未被访问过的邻接点出发进行深度优先搜索遍历。
如此执行,直到退回到初始点并且没有未被访问过的邻接点为止。
(2)广度优先搜索遍历:
广度优先搜索过程为:
首先访问初始点Vi,并将其标记为已访问过,接着访问Vi的所有未被访问过的邻接点,其访问顺序可以任意,假定依次为Vi1、Vi2,…Vin,并标记为已访问过,然后按照Vi1、Vi2,…Vin的次序访问每一个顶点的所有未被访问过的邻接点,并均标记为已访问过,依次类推,直到图中所有和初始点Vi有路径相通的顶点都被访问过为止。
在设计迷宫问题时要考虑使用二维数组,在数组中我们选择maze[N+2][N+2]来表示迷宫,而不是用maze[N][N]来表示,这样就可以避免老鼠走迷宫会出格。
通过这一个星期的学习实践,我更加深层次的了解了关于数据结构的相关知识,也越来越发现自己对数据结构方面知识的欠缺,使我对自己所学得的知识有了一个深刻的理解,对于这方面的知识,我还缺少很多,纸上得来终觉浅,再强大的理论也要通过实践来证明。
在今后的学习中我要多练习,做一个专业的计算机学生。
参考文献
[1]刘振安,刘燕君.C程序设计课程设计[M].[北京]机械工业出版社,2004年9月
[2]谭浩强.C程序设计(第三版).清华大学出版社,2005年7月
[3]严蔚敏,吴伟民.数据结构(C语言版).清华大学出版社,1997年4月
[4]李志球《实用C语言程序设计教程》北京:
电子工业出版社,1999
[5]王立柱:
C/C++与数据结构北京:
清华大学出版社,2002
[6]吴文虎《程序设计基础》北京:
清华大学出版社,2003
[7]郭福顺,王晓芬,李莲治《数据结构》(修订本),大连理工大学出版社,1997
[8]潘道才,陈一华《数据结构》,电子科技大学出版社,1994
附录
附录1源程序清单
#include//库中包含system("pause")和rand()函数
#include//c语言里的库
#include
usingnamespacestd;
#defineN49//定义为全局变量,这是迷宫数组的上线,可以自行修改
#defineM49
intX;
intmaze[N+2][M+2];
inthead=0,tail=0;//队列的头尾指针,初始值设为0
structpoint//存放迷宫访问到点的队列结构体,包含列,行,序号
{
introw,col,predecessor;
}queue[1200];
voidhand_maze(intm,intn)//手动生成迷宫
{
inti,j;
cout<cout<<"请按行输入迷宫,0表示通路,1表示障碍:
"<for(i=0;ifor(j=0;j{
cin>>maze[i][j];
}
}
voidautomatic_maze(intm,intn)//自动生成迷宫
{
inti,j;
cout<<"\n迷宫生成中……\n\n";
system("pause");
for(i=0;ifor(j=0;jmaze[i][j]=rand()%2;//随机生成0、1
maze[0][0]=0;//将开始和结束位置强制为0,保证有可能出来迷宫
maze[m-1][n-1]=0;
}
voiddata(intm,intn)
{//当用户输入的不是规整的m行n列的迷宫,用来生成规则的数字迷宫
inti,j;
cout<cout<<"根据您先前设定的迷宫范围"<cout<cout<<"我们将取所输入的前"<cout<<"\n数字迷宫生成结果如下:
\n\n";
cout<<"迷宫入口\n";
cout<<"↓";
for(i=0;i{
cout<<"\n";
for(j=0;j{
if(maze[i][j]==0)
cout<<"0";
if(maze[i][j]==1)
cout<<"1";
}
}
cout<<"→迷宫出口\n";
}
voidprint_maze(intm,intn)
{//打印迷宫外壳
inti,j,k;
cout<<"\n字符迷宫生成结果如下:
\n\n";
cout<<"迷宫入口\n";
cout<<"↓";
cout<cout<<"▲";//生成上外壳
for(k=0;k{
cout<<"▲";//这两个黑三角用来生成顶部外壳
}
for(i=0;i{
cout<<"\n";//生成左外壳
cout<<"▲";
for(j=0;j{
if(maze[i][j]==0)printf("□");
if(maze[i][j]==1)printf("■");
}
cout<<"▲";//生成右外壳
}
cout<for(k=0;k{
cout<<"▲";
}
cout<<"▲\n";//生成底部外壳
for(i=0;i{cout<<"";}
cout<<"↓\n";
for(i=0;i{cout<<"";}
cout<<"迷宫出口\n";
}
voidresult_maze(intm,intn)//这个是打印输出迷宫的星号路径
{
inti,j;
cout<<"迷宫通路(用☆表示)如下所示:
\n\t";
for(i=0;i{
cout<<"\n";
for(j=0;j{
if(maze[i][j]==0||maze[i][j]==2)//2是队列中访问过的点
cout<<"□";
if(maze[i][j]==1)
cout<<"■";
if(maze[i][j]==3)//3是标记的可以走通的路径
cout<<"☆";
}
}
}
voidenqueue(structpointp)//迷宫中队列入队操作
{
queue[tail]=p;
tail++;//先用再加,队列尾部加1
}
structpointdequeue()//迷宫中队列出队操作,不需要形参,因此用结构体定义
{
head++;
returnqueue[head-1];
}
intis_empty()//判断队列是否为空
{
returnhead==tail;
}
voidvisit(introw,intcol,intmaze[51][51])//访问迷宫矩阵中的节点
{
structpointvisit_point={row,col,head-1};//head-1的作用是正在访问的这个点的序号为之前的点序号
maze[row][col]=2;//将访问过的点位标记为2
enqueue(visit_point);//入队
}
intpath(intmaze[51][51],intm,intn)//路径求解
{
X=1;//初始值定为1
structpointp={0,0,-1};//定义入口节点
if(maze[p.row][p.col]==1)//入口为1时,迷宫不可解
{
cout<<"\n===============================================\n";
cout<<"此迷宫无解\n\n";
X=0;
return0;
}
maze[p.row][p.col]=2;//标记为已访问
enqueue(p);//将p入队列
while(!
is_empty())
{
p=dequeue();
if((p.row==m-1)&&(p.col==n-1))//当行和列为出口时跳出
break;
//定义8个走位方向
if((((p.row-1)>=0)&&((p.row-1)visit(p.row-1,p.col+0,maze);//北
if((((p.row-1)>=0)&&((p.row-1)visit(p.row-1,p.col+1,maze);//东北
if((((p.row+0)visit(p.row+0,p.col+1,maze);//东
if((((p.row+1)visit(p.row+1,p.col+1,maze);//东南
if((((p.row+1)visit(p.row+1,p.col+0,maze);//南
if((((p.row+1)=0))&&(maze[p.row+1][p.col-1]==0))
visit(p.row+1,p.col-1,maze);//西南
if((((p.row+0)=0))&&(maze[p.row+0][p.col-1]==0))
visit(p.row+0,p.col-1,maze);//西
if((((p.row-1)>=0)&&((p.row-1)=0))&&(maze[p.row-1][p.col-1]==0))
visit(p.row-1,p.col-1,maze);//西北
}
if(p.row==m-1&&p.col==n-1)//如果当前矩阵点是出口点,输出路径
{
cout<<"\n==================================================================\n";
cout<<"迷宫路径为:
\n";
cout<<"出口"<cout<<""<<"↑"<printf("(%d,%d)\n",p.row+1,p.col+1);
cout<<""<<"↑"<maze[p.row][p.col]=3;//逆序将路径标记为3
while(p.predecessor!
=-1)
{
p=queue[p.predecessor];
printf("(%d,%d)\n",p.row+1,p.col+1);
cout<<""<<"↑"<maze[p.row][p.col]=3;
}
cout<<"入口"<}
else
{
cout<<"\n=============================================================\n";
cout<<"此迷宫无解!
\n\n";
X=0;
}
return0;
}
voidmain()
{
inti,m,n,cycle=0;
while(cycle!
=(-1))
{
cout<<"********************************************************************************\n";
cout<<"欢迎进入迷宫求解系统\n";
cout<cout<<"设计者:
李柏(B计算机115班)\n";
cout<<"********************************************************************************\n";
cout<<"☆手动生成迷宫请按:
1\n";
cout<<"☆自动生成迷宫请按:
2\n";
cout<<"☆退出请按:
3\n\n";
cout<<"********************************************************************************\n";
cout<