当前位置:文档之家› 深度与广度优先搜索:迷宫问题

深度与广度优先搜索:迷宫问题

《数据结构课程设计》报告题目:深度与广度优先搜索--迷宫问题专业计算机科学与技术学生姓名李柏班级B计算机115学号1110704512指导教师巩永旺完成日期2013年1月11日目录1简介 (1)2算法说明 (1)3测试结果 (3)4分析与探讨 (7)5小结 (9)附录 (10)附录1 源程序清单 (10)迷宫问题1 简介1、图的存储结构图的存储结构又称图的表示,其最常用的方法是邻接矩阵和邻接表。

无论采用什么存储方式,其目标总是相同的,既不仅要存储图中各个顶点的信息,同时还要存储顶点之间的所有关系。

2、图的遍历图的遍历就是从指定的某个顶点(称其为初始点)出发,按照一定的搜索方法对图中的所有顶点各做一次访问过程。

根据搜索方法不同,遍历一般分为深度优先搜索遍历和广度优先搜索遍历。

本实验中用到的是广度优先搜索遍历。

即首先访问初始点v i,并将其标记为已访问过,接着访问v i的所有未被访问过的邻接点,顺序任意,并均标记为已访问过,以此类推,直到图中所有和初始点v i有路径相通的顶点都被访问过为止。

鉴于广度优先搜索是将所有路径同时按照顺序遍历,直到遍历出迷宫出口,生成的路径为最短路径。

因此我们采用了广度优先搜索。

无论是深度优先搜索还是广度优先搜索,其本质都是将图的二维顶点结构线性化的过程,并将当前顶点相邻的未被访问的顶点作为下一个顶点。

广度优先搜索采用队列作为数据结构。

本实验的目的是设计一个程序,实现手动或者自动生成一个n×m矩阵的迷宫,寻找一条从入口点到出口点的通路。

具体实验内容如下:选择手动或者自动生成一个n×m的迷宫,将迷宫的左上角作入口,右下角作出口,设“0”为通路,“1”为墙,即无法穿越。

假设一只老鼠从起点出发,目的为右下角终点,可向“上、下、左、右、左上、左下、右上、右下”8个方向行走。

如果迷宫可以走通,则用“■”代表“1”,用“□”代表“0”,用“☆”代表行走迷宫的路径。

输出迷宫原型图、迷宫路线图以及迷宫行走路径。

如果迷宫为死迷宫,则只输出迷宫原型图。

2算法说明迷宫中存在通路和障碍,为了方便迷宫的创建,可用0表示通路,用1表示障碍,这样迷宫就可以用0、1矩阵来描述。

设置迷宫的长为n、宽为m,范围为49×49,用int maze[N+2][M+2]来表示,这样相当于在迷宫外层包了一层1,即防止搜索路径时跳出迷宫。

(1)手动生成迷宫void hand_maze(int m,int n) //手动生成迷宫{int i,j;for(i=0;i<m;i++)for(j=0;j<n;j++){cin>>maze[i][j];}}(2)自动生成迷宫void automatic_maze(int m,int n) //自动生成迷宫{int i,j;for(i=0;i<m;i++)for(j=0;j<n;j++)maze[i][j]=rand()%2; //随机生成0、1maze[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。

实验数据如下:Name dir Move[dir].vert Move[dir].horiz N 0 -1 0NE 1 -1 1E 2 0 1SE 3 1 1S 4 1 0SW 5 1 -1W 6 0 -1NW 6 0 -13测试结果图1图2图3图4图5图6图74分析与探讨首先明确题目中的已知条件:(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的偏移量迷宫问题可以用深度优先搜索方法实现。

当老鼠在迷宫中移动的时候,可能会有许多种移动选择方向。

程序需要记录并用栈来保存当前点的坐标,然后任意选择一个方向进行移动。

由于应用栈保存了当前通道上各点的坐标,所以当在当前各方向上都走不通时可以返回上一个点,然后选择另一个方向前进。

可定义element结构用于存储每一步的横纵坐标和方向。

typedef struct{short int row;short int col;short int dir;}element;Element stack[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()函数来划线。

程序的主要函数如下:●函数void add(int*top,element item),将当前步的信息item压入到作为全局变量的栈stack(栈顶为top)中。

●函数element delete(int * top),返回stack中栈顶的元素。

●函数void path(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<stdlib.h> //库中包含system("pause")和rand()函数#include<stdio.h> //c语言里的库#include<iostream>using namespace std;#define N 49 //定义为全局变量,这是迷宫数组的上线,可以自行修改#define M 49int X;int maze[N+2][M+2];int head=0,tail=0; //队列的头尾指针,初始值设为0struct point //存放迷宫访问到点的队列结构体,包含列,行,序号{int row,col,predecessor;}queue[1200];void hand_maze(int m,int n) //手动生成迷宫{int i,j;cout<<endl;cout<<"请按行输入迷宫,0表示通路,1表示障碍:"<<endl;for(i=0;i<m;i++)for(j=0;j<n;j++){cin>>maze[i][j];}}void automatic_maze(int m,int n) //自动生成迷宫{int i,j;cout<<"\n迷宫生成中……\n\n";system("pause");for(i=0;i<m;i++)for(j=0;j<n;j++)maze[i][j]=rand()%2; //随机生成0、1maze[0][0]=0; //将开始和结束位置强制为0,保证有可能出来迷宫maze[m-1][n-1]=0;}void data(int m,int n){ //当用户输入的不是规整的m行n列的迷宫,用来生成规则的数字迷宫int i,j;cout<<endl;cout<<"根据您先前设定的迷宫范围"<<endl;cout<<endl;cout<<" 我们将取所输入的前"<<m*n<<"个数生成迷宫"<<endl;cout<<"\n数字迷宫生成结果如下:\n\n";cout<<"迷宫入口\n";cout<<"↓";for(i=0;i<m;i++){cout<<"\n";for(j=0;j<n;j++){if(maze[i][j]==0)cout<<" 0";if(maze[i][j]==1)cout<<" 1";}}cout<<"→迷宫出口\n";}void print_maze(int m,int n){ //打印迷宫外壳int i,j,k;cout<<"\n字符迷宫生成结果如下:\n\n";cout<<"迷宫入口\n";cout<<" ↓";cout<<endl;cout<<"▲"; //生成上外壳for(k=0;k<n;k++){cout<<"▲"; //这两个黑三角用来生成顶部外壳}for(i=0;i<m;i++){cout<<"\n"; //生成左外壳cout<<"▲";for(j=0;j<n;j++){if(maze[i][j]==0) printf("□");if(maze[i][j]==1) printf("■");}cout<<"▲"; //生成右外壳}cout<<endl;for(k=0;k<n;k++){cout<<"▲";}cout<<" ▲\n"; //生成底部外壳for(i=0;i<n;i++){ cout<<" ";}cout<<"↓\n";for(i=0;i<n;i++){ cout<<" ";}cout<<"迷宫出口\n";}void result_maze(int m,int n) //这个是打印输出迷宫的星号路径{int i,j;cout<<"迷宫通路(用☆表示)如下所示:\n\t";for(i=0;i<m;i++){cout<<"\n";for(j=0;j<n;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<<"☆";}}}void enqueue(struct point p) //迷宫中队列入队操作{queue[tail]=p;tail++; //先用再加,队列尾部加1}struct point dequeue() //迷宫中队列出队操作,不需要形参,因此用结构体定义{head++;return queue[head-1];}int is_empty() //判断队列是否为空{return head==tail;}void visit(int row,int col,int maze[51][51]) //访问迷宫矩阵中的节点{struct point visit_point={row,col,head-1}; //head-1的作用是正在访问的这个点的序号为之前的点序号maze[row][col]=2; //将访问过的点位标记为2 enqueue(visit_point);//入队}int path(int maze[51][51],int m,int n) //路径求解{X=1; //初始值定为1struct point p={0,0,-1}; //定义入口节点if(maze[p.row][p.col]==1) //入口为1时,迷宫不可解{cout<<"\n===============================================\n";cout<<"此迷宫无解\n\n";X=0;return 0;}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)<m)&&((p.col+0)<n))&&(maze[p.row-1][p.col+0]==0))visit(p.row-1,p.col+0,maze); //北if((((p.row-1)>=0)&&((p.row-1)<m)&&((p.col+1)<n))&&(maze[p.row-1][p.col+1]==0))visit(p.row-1,p.col+1,maze); //东北if((((p.row+0)<m)&&((p.col+1)<n))&&(maze[p.row+0][p.col+1]==0))visit(p.row+0,p.col+1,maze); //东if((((p.row+1)<m)&&((p.col+1)<n))&&(maze[p.row+1][p.col+1]==0))visit(p.row+1,p.col+1,maze); //东南if((((p.row+1)<m)&&((p.col+0)<n))&&(maze[p.row+1][p.col+0]==0))visit(p.row+1,p.col+0,maze); //南if((((p.row+1)<m)&&((p.col-1)<n)&&((p.col-1)>=0))&&(maze[p.row+1][p.col-1]==0))visit(p.row+1,p.col-1,maze); //西南if((((p.row+0)<m)&&((p.col-1)<n)&&((p.col-1)>=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)<m)&&((p.col-1)<n)&&((p.col-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<<"出口"<<endl;cout<<" "<<"↑"<<endl;printf("(%d,%d)\n",p.row+1,p.col+1);cout<<" "<<"↑"<<endl;maze[p.row][p.col]=3; //逆序将路径标记为3while(p.predecessor!=-1){p=queue[p.predecessor];printf("(%d,%d)\n",p.row+1,p.col+1);cout<<" "<<"↑"<<endl;maze[p.row][p.col]=3;}cout<<"入口"<<endl;}else{cout<<"\n====================================================== =======\n";cout<<"此迷宫无解!\n\n";X=0;}return 0;}void main(){int i,m,n,cycle=0;while(cycle!=(-1)){cout<<"********************************************************************************\n";cout<<" 欢迎进入迷宫求解系统\n";cout<<endl;cout<<" 设计者:李柏(B计算机115班)\n";cout<<"********************************************************************************\n";cout<<" ☆手动生成迷宫请按:1\n";cout<<" ☆自动生成迷宫请按:2\n";cout<<" ☆退出请按:3\n\n";cout<<"********************************************************************************\n";cout<<"\n";cout<<"请选择你的操作:\n";cin>>i;switch(i){case 1:cout<<"\n请输入行数:";cin>>m;cout<<"\n";cout<<"请输入列数:";cin>>n;while((m<0||m>49)||(n<0||n>49)){cout<<"\n抱歉,你输入的行列数超出预设范围(0-49,0-49),请重新输入:\n\n";cout<<"\n请输入行数:";cin>>m;cout<<"\n";cout<<"请输入列数:";cin>>n;}hand_maze(m,n);data(m,n);print_maze(m,n);path(maze,m,n);if(X!=0) result_maze(m,n); //当X不为0时,有解,调用输出路径函数cout<<"\n\nPress Enter Contiue!\n";getchar();while(getchar()!='\n'); //接受一个输入,当为回车时执行break跳出,否则一直执行接受数据break;case 2:cout<<"\n请输入行数:";cin>>m;cout<<"\n";cout<<"请输入列数:";cin>>n;while((m<0||m>49)||(n<0||n>49)){cout<<"\n抱歉,你输入的行列数超出预设范围(0-49,0-49),请重新输入:\n\n";cout<<"\n请输入行数:";cin>>m;cout<<"\n";cout<<"请输入列数:";cin>>n;}automatic_maze(m,n);data(m,n);print_maze(m,n);path(maze,m,n);if(X!=0) result_maze(m,n);cout<<"\n\nPress Enter Contiue!\n";getchar();while(getchar()!='\n');break;case 3:cycle=(-1);break;default:cout<<"\n";cout<<"你的输入有误!\n";cout<<"\nPress Enter Contiue!\n";getchar();while(getchar()!='\n');break;}}}。

相关主题