# 迷宫生成器
张桐桐,李玉璇,孟婷玉,周羽凡
**摘要**:设计一个程序随机生成指定大小的迷宫。一个迷宫是由 $M \times N$ 个方格组成的矩形区域,每个方格的四周可能存在墙,相邻的两个方格之间如果没有墙的阻隔则可以通行,否则无法通行。要求系统运行正常、功能完整;数据结构使用得当,算法有较高的效率;代码规范、可读性高,结构清晰;具备一定的健壮性、可靠性和可维护性。
任务分工及完成情况。
每个成员的工作量(百分比):
| 张桐桐 | 李玉璇 | 周羽凡 |孟婷玉|
| ---- | ---- | ---- |----|
| 30 | 30 | 20 | 20 |
# 1. 系统分析
## 1.1 问题描述
综合运用线性表、栈和队列、图、查找和排序等数据结构知识,设计一个程
序能够随机构造指定大小的迷宫,掌握和提高分析、设计、实现及测试程序
的综合能力。
要求
设计一个程序随机生成指定大小的迷宫。一个迷宫是由 m*n 个方格组
成的矩形区域,每个方格的四周可能存在墙,相邻的两个方格之间如果没有
墙的阻隔则可以通行,否则无法通行。
( 1) 指定迷宫的大小为 m 行 n 列,随机生成迷宫。
( 2) 任意两个方格之间都存在通路。
( 3) 在不重复通过的情况下, 任意两个方格之间有且只有一条通路。
( 4) 用文本文件保存迷宫, 包括迷宫的大小和每一个被敲掉的墙。
( 5) 尝试以图形窗口或文本方式显示迷宫。
要求系统运行正常、功能完整;数据结构使用得当,算法有较高的效率;代
码规范、可读性高,结构清晰;具备一定的健壮性、可靠性和可维护性。
## 1.2 可行性分析
如果让所有存在通路的单元格组成一个集合,则构造迷宫可以看作是不断打破不同集合之间的墙,直至合并成一个集合的过程。迷宫的初始状态包含所
有的墙,每个单元格属于一个独立的集合。随机敲掉两个集合之间的一个墙使两者相通,同时令两个集合合并成一个集合。不断重复,直到所有单元格
都属于一个联通单元格的集合
对于迷宫,我们一般是用一个二元的数组来存储迷宫中的单元,不同符号表示墙和可以通过的路径。我们的目的是在这个迷宫中找到一条从起点到终点的全部是可通过路径的单元组。
从起点出发,向其四周探索,如果是可通过单元且没有访问过,则将其入栈并标记为已访问单元,并继续探索其它方向,如果其四周没有未访问的可通过单元,则说明该点为死点,我们就将其出栈。如果碰到终点,则停止探索,同时回溯栈中的点,把栈中的路径中的单元标记出来。
## 1.3 需求分析
( 1) 输入一个任意大小的迷宫数据, 用非递归的方法求出一条走出迷宫的路径, 并将路径输出;
输入行和列的长度,来设置迷宫的大小;
( 2) 对迷宫, 用栈来进行处理, 栈是后进先出的线性数据结构, 当每走一格时, 就对上一格的坐标和向下一格要走的方向进行记录并规定每当遇到死路, 即四个方向的“通路成立判断”都不成立时, 从栈中取出栈顶元素, 就这样到当前坐标值等于终点坐标值时, 及循环停止时, 栈中的所有元素自下而上就是对路径的全部描述。
( 3) 读取输入的迷宫长度( m*n) , 对迷宫进行搜索路径, 找到最短的路径
( 4) 迷宫地图的大小和迷宫的入口位置
( 5) 合并功能, 能够使用户更方便的查看搜索结果, 将多个数据合并成一个能够进行更高效的操作。
( 6) 递归功能, 递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算, 大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。
# 2. 系统设计
## 2.1 概要设计
系统主要划分为6个模块.首先用宽搜来寻找最短路径的长度,然后用深搜来找出所有路径,并且标记出其中的最短的路径。其中宽搜采用的是队列,深搜采用的是栈。思路都是去寻找可以走的几个方向,逐个判断就行了。
## 2.2 数据结构设计
设计步骤:
(1)构建一个二维数组 maze [ M +2][ N +2]用于存储迷宫矩阵
(2)自动或手动生成迷宫,即为二维数组
(4)构建一个队列用于存储迷宫路径
(4)建立迷宫节点 struct point ,用于存储迷宫中每个节点的访问情况
maze [ M +2][ N +2]赋值
(5)实现搜索算法
(6)屏幕上显示操作菜单
该项目的主要10个函数:
(1)主函数 main ()
(2)手动生成迷宫函数 shoudong _ maze ()
(3)自动生成迷宫函数 zidong _ maze ()
(4)将迷宫打印成图形 print _ maze ()
(5)打印迷宫路径(若存在路径) result _ maze ()
(6)入队 enqueue ()
(7)出队 dequeue ()
(8)判断队列是否为空 is _ empty ()
(9)访问节点 visit ()
(10) 搜索迷宫路径 mgpath ()
### ( 1) 结构
1. 节点类型和指针类型
迷宫矩阵类型
迷宫中节点类型及队列类型
2.迷宫的操作
(1)手动生成迷宫
(2)自动生成迷宫
(3)打印迷宫图形
3. 菜单选择
## 2.3 算法设计
我们实现了一种可实现数据结构, 并且运用的数据结构都能完整的实现生成所需迷宫。考虑到我们现有的开发工具只有git与vscode,目前同学所学知识主要是队列,栈和图等基本的数据结构基础,因此还是选用一种数据结构方法实现迷宫生成。
### ( 1) 算法
1. 节点类型和指针类型
迷宫矩阵类型: int maze [ M +2][ N +2];为方便操作使其为全局变量
迷宫中节点类型及队列类型: struct pointlint row , col , predecessor } que [512]
2.迷宫的操作
(1) 手动生成迷宫
void shoudong _ maze ( int m , int n )
(定义 i , j 为循环变量
for ( i < = m )
for ( j < = n )
输入 maze [ i ][ j ]的值
(2) 自动生成迷宫
void zidong _ maze ( int m , int n )
(定义 i , j 为循环变量
for ( i < = m )
for ( j < = n )
maze [ i ][ j ]= rand ()%2
//由于 rand () 产生的随机数是从0到 RAND _ MAX , RAND _ MAX 是定义在 stdlib . h 中的, 其值至少为32767),要产生从 X 到 Y 的数,只需要这样写: k = rand ()%( Y - X +1)+ X ;
(3) 打印迷宫图形
void print _ maze ( int m , int n )
(用 i , j 循环变量,将 maze [ i ][ j ]输出口、)(4)打印迷宫路径
void result _ maze ( int m , int n )
(用 i , j 循环变量,将 maze [ i ][ j ]输出口、、☆](5) 搜索迷宫路径
①迷宫中队列入队操作
void enqueue ( struct point p )
[将 p 放入队尾, tail ++
②迷宫中队列出队操作
struct point dequeue ( struct point p )
{ head ++,返回 que [ head -1]]
③判断队列是否为空
int is _ empty ()
{返回 head == tail 的值, 当队列为空时, 返回0④访问迷宫矩阵中节点
void visit ( int row , int col , int maze [41][41])
{建立新的队列节点 visit _ point ,将其值分别赋为 row , col , head -1, maze [ row ][ col ]=2, 表示该节点以被访问过: 调用
enqueue ( visit _ point ),将该节点入队)
⑤路径求解
void mgpath ( int maze [41][41], int m , int n )
(先定义入口节点为 struct point p =(0,0,-1),从 maze [0][0] 开始访问。如果入口处即为障碍, 则此迷宫无解, 返回0, 程序结束。否则访问入口节点, 将入口节点标记为访问过 maze [ p . row ][ p . col ]=2,调用函数 enqueue ( p )将该节点入队。
判断队列是否为空,当队列不为空时,则运行以下操作:
(调用 dequeue ()函数,将队头元素返回给 p ,
如果 p . row == m -1且 p . col == n -1, 即到达出口节点, 即找到了路径, 结束
如果 p . col +1< n 且 maze [ p . row ] [ p . co1 + 1 ] = = 0 , 说 明 未 到 迷 宫 右 边 界 , 且 其 右 方 有 通 路 , 则 visit ( p . row , p . col + 1 , maze ) , 将 右 边 节 点 入 队 标 记 己 访 问
如果 p . row +1< m 且 maze [ p . row + 1 ] [ p . col ] = = 0 , 说 明 未 到 迷 宫 下 边 界 , 且 其 下 方 有 通 路 , 则 visit ( p . row + 1 , p . col , maze ) , 将 下 方 节 点 入 队 标 记 已 访 问
如果 p . col -1>0且 maze [ p . row ][ p .co1-1]==0, 说明未到迷宫左边界, 且其左方有通路, 则 visit ( p . row , p . col -1, maze ),将左方节点入队标记已访问
如果 p . row -1>0且 maze [ p . row -1][ p . col ]==0, 说明未到迷宫上边界, 且其上方有通路, 则 visit ( p . row , p . col +1, maze ),将上方节点入队标记已访问
访问到出口(找到路径)即 p . row == m -1且 p . col == n -1, 则逆序将路径标记为3即 maze [ p . row ][ p . col ]==3;
while ( p . predecessor !=-1)
[ p = queue [ p . predecessor ]; maze [ p . row ][ p . col ]==3;)
最后将路径图形打印出来。
3. 菜单选择
while ( cycle !=(-1))
☆手动生成迷宫
请按: 1
请按: 2
请按: 3
☆退出
scanf ("% d ",& i );
switch ( i )
☆自动生成迷宫
☆退出
scanf ("% d ",& i );
switch ( i )
( case 1: 请输入行列数( 如果超出预设范围则提示重新输入
请按: 2
请按: 3
shoudong _ maze ( m , n );
print _ maze ( m , n );
mgpath ( maze , m , n );
if ( X !=0) result _ maze ( m , n );
case 2: 请输入行列数( 如果超出预设范围则提示重新输入
zidong _ maze ( m , n );
print _ maze ( m , n );
mgpath ( maze , m , n );
if ( X !=0) result _ maze ( m , n );
case 3:cycle=(-1); break ;
# 3. 系统实现
## 3.1 核心数据结构的实现
描述数据结构的实现方法。
int maze[M][N],row,col;
typedef struct 栈操作函数
void Init_hand_Maze(int maze[M][N],int m,int n)
{int i,j;
for(i=1;i< =m+1;i++)
for(j=1;j< =n+1;j++)
{maze[i][j]=1;
}
cout< < " 请按行输入迷宫, 0表示通路, 1 表示障碍:"< < endl ; for ( i = 1;i<m+1;i++)
for(j=1.j< n + 1 ; j + + )
cin>>maze[i][j];
for(i=1;i< m + 1 ; i + + ) {
for(j=1:j< n + 1 ; j + + )
{
if(maze[i][j]!=0& & maze[i][j]!=1)(
cout< < ” 您输入有误,请重新输入";
Init_hand_Maze(maze,m,n);
}
}
}
}
时间复杂度 O( m*n)
void Init_automatic_Maze(int maze[M][N],int m,int n) 求解迷宫
Status MazePath(Stack & S,MazeType & e,int maze[M][N], int m, int n)
{
do
{
if(maze[][]==0) 打印路径
int PrintPath(Stack S,int maze[M][N],int row,int col)
{
if==
cout<< "\n===============================================\n";
cout<< " 此迷宫无解 \n\n";
return ERROR;
}
MazeType e; while!=
{Pop(S,e); maze[][]=+10);}
cout<< "\n===============================================\n";
cout< < " 路径为 :"< < endl ;
int i,j:
for(i=1;i< row + 1 ; i + + )
{
for(j=1;j< col + 1:j + + )
{
switch(maze[i][j])
{
case 0:
cout< < " 口";
break;
case 1:
cout< < " ■";
break;
case 2:
cout< < "※”;
break;
case 10:
cout< < " →";
break;
case 11:
cout< < " ↓";
break;
case 12:
cout< < " ←";
break;
case 13:
cout< < " ↑";
break;
}
}
cout< < endl ;
}
cout< < " 完成!"< < endl ;
return OK;
}
## 3.2 核心算法的实现
描述算法的实现方法。
主函数
int main()
{
switch(i)
{
case 1:
Init_hand_Maze(maze,m,n);
PrintMaze(maze,m,n);
InitStack(S);
MazePath(S,start,maze,,;
PrintPath(S,maze,m,n);
break;
case 2:
Init_automatic_Maze(maze,m,n);
PrintMaze(maze,m,n);
initStack(S):
MazePath(S,start,maze,,;
PrintPath(S,maze,m,n);
break;
case 3:
cycle=(-1);break;
default:
cout<< "\n";cout<< " 你的输入有误!\n”;
break;
}
}
}
# 4. 系统测试
描述测试的思路和方法。比如,先用小数据量进行测试,再用真实数据进行测试。
测试应考虑到输入数据的特殊情况。
输入 1 1 构不成迷宫
输入 1000 1000 位置不足
给出若干测试用例,包括输入、预期结果、运行结果或是否通过测试。运行结果和预期结果一致,为通过测试。
测试 输入 10 8
```
+ + + + + + + + +
| | |
+ +---+---+---+ + +---+ +
| | | |
+---+ +---+ + + +---+---+
| | | | |
+ + + + +---+ +---+ +
| | | |
+---+---+ +---+---+---+---+ +
| | | |
+ +---+ + + + + +---+
| | | |
+---+ +---+---+ + +---+---+
| | | |
+ +---+---+ +---+---+ +---+
| | | |
+---+---+ + +---+ +---+ +
| | | |
+ + + + +---+ + + +
| | | |
+ + + + +---+ + + +
```
10 10
```
+ + + + + + + + + + +
| | | | |
+ +---+ +---+ + + +---+---+ +
| | | | |
+ + +---+ +---+---+ +---+ +---+
| | | | | |
+---+ +---+ +---+ +---+ +---+ +
| | | | |
+ + + +---+---+ +---+---+---+ +
| | |
+---+ + +---+ +---+ +---+---+---+
| | | | |
+ + +---+---+ +---+ +---+---+---+
| | |
+ + +---+ + +---+---+ +---+ +
| | | | |
+ +---+ +---+ +---+---+ + + +
| | | | |
+ +---+ + + + + +---+---+ +
| | | | | | |
+ +---+ + + + + +---+---+ +
```
15 15
```
+ + + + + + + + + + + + + + + +
| | | | | | | |
+ + + + + + +---+ +---+ +---+ + +---+ +
| | | | | | | | |
+---+ + +---+ + +---+---+---+ + +---+ + + +
| | | | | |
+ +---+---+---+ +---+ +---+---+ + +---+---+ + +
| | | | | | |
+ + + + +---+ +---+ + + + + +---+ + +
| | | | | | | | | | | | |
+ +---+ +---+ +---+ + +---+ +---+---+ + +---+
| | | | | |
+---+ + +---+ +---+ + +---+---+ +---+ +---+---+
| | | | | | | |
+ +---+ +---+---+---+---+---+---+---+ +---+---+ + +
| | | | |
+ +---+ +---+---+---+---+ + + + + +---+ + +
| | | | | | | | | | | |
+---+ + + + + +---+ +---+ + + +---+---+ +
| | | | | |
+ +---+ + +---+ +---+ + +---+---+ +---+---+ +
| | | | | | | |
+ +---+ +---+---+---+---+---+---+---+---+---+ +---+ +
| | | | | | | | |
+ +---+ +---+ + +---+ + +---+ +---+ + + +
| | | | | | |
+ + + + + + +---+---+ +---+ + +---+ + +
| | | | | | | |
+ + +---+---+---+ +---+ +---+ + +---+ + +---+
| | | | | | | |
+ + +---+---+---+ +---+ +---+ + +---+ + +---+
```
均成功构成迷宫
# 5. 总结
概况项目和完成情况。
遇到的问题和解决方法。
个人小结:
张桐桐:完成迷宫这个项目,首先需要了解输入和输出,目标明确才可以进行下一步,其中运用到不相交集的知识,需要去自己学习,给我触发很大,计算机行业发展迅速,就算将来进行工作也离不开学习新的知识,所以在大学培养自我学习是非常重要的。没有什么是可以不劳而获的,只有贫穷可以。任务分为代码和文档两个部分,如何合理的分工,让每个人都有自己的任务,是非常重要的事情。这次的项目和之前的项目有所不同,没有可以参考的,只有自己去思考去尝试,将所学的知识合理的应用在恰当的部分。学以致用,才符合项目的目的。
李玉璇:对于迷宫这个课程设计,了解了怎样制作迷宫,对这个问题确实不简单,也需要动脑子思考,运用到了所学的许多知识,归并合并等,给我了很大的触发,让我了解了迷宫是怎样在电脑中运行的,让我看到了迷宫后面的程序。这个项目也是经过小组合作,各个人完成各自的任务,经过配合团结共同完成任务。在后来的工作当中这是必不可少的一部分,在未来的工作当中也需要团队的合作和任务的分配,对任何的一个项目都需要勇于尝试,不管做的对错与否,错了可以进行修改,参与进去是很重要的。对所学知识有很好的应用是很重要的。
周羽凡:在这次的迷宫项目中,我们小组通过分工合作完成了这次设计,对于此次困难的问题,我们所有人并没有选择退缩,这也让我感受到了信心。于是在一次次的错误以及失败中我们不断改进,然后取得了丰厚的成果,这也让我了解到只要我们坚持不懈没有什么难题是解决不了的。我也认识到合作的重要性,将一个难题分为几个部分,小组成员都一起努力,逐个击破难题,不仅出色的完成了任务,也大大缩短了完成项目所需要的时间。
孟婷玉:迷宫课程项目设计非常之难,这让我们一开始无从下手。后来我们明确,完成迷宫这个课程设计这个项目,需要先整体构建起框架,在思考输入和输出分别具体是什么,然后思考需要运用那些知识,已会的知识需要去灵活的搬运和应用,如数组和栈和图;不会的知识点需要通过自己去学习,去网上搜索资料,去查阅老师给予的可帮助资料来学习,然后才能开始投入到项目中去。大家一起完成一个项目,才会高效有益,合作使得大家都可以用合理和相对较短的时间,完成庞大的项目。另外,我们也发现,项目及时差错是非常重要的。
# 参考文献
列出参考的文献资料,根据情况自行添加。
[1] 严蔚敏, 吴伟民. 数据结构( C语言版) . 北京: 清华大学出版社, 2007.
[2]DATA STRUCTURES and ALGORITHM ANALISIS in C++