迷宫求解问题资料.docx
《迷宫求解问题资料.docx》由会员分享,可在线阅读,更多相关《迷宫求解问题资料.docx(19页珍藏版)》请在冰豆网上搜索。
迷宫求解问题资料
迷宫求解问题
摘 要:
用矩阵表示迷宫,将矩阵表示的迷宫转换成无向图,用邻接表存储。
对无向图从入口结点开始广度优先搜索,用一个一维数组存储各个结点的前驱结点的编号,通过出口结点Vn找到其前驱结点Vn-1,再通过Vn-1找到Vn-2,依次类推直到找到出口结点。
关键字:
矩阵 迷宫求解
一、需求分析
1.程序题目:
迷宫求解问题。
迷宫是一个如下所示的m行n列的0-1矩阵,0表示无障碍,1表示有障碍。
设入口为(1,1),出口为(m,n),每次移动只能从一个无障碍的单元移到周围8个方向的任意一个无障碍的单元,编写程序给出一条通过迷宫的路径或者报告一个“无法通过”的信息。
入口->(0,0,0,1,0,0,0,1,0,0,0,1,0,0,1)
(0,1,0,0,0,1,0,1,0,0,0,1,1,1,1)
(0,1,1,1,1,1,0,1,0,0,1,1,1,0,1)
(1,1,0,0,0,1,1,0,1,1,0,0,1,0,1)
(1,0,0,1,0,1,1,1,1,0,1,0,1,0,1)
(1,0,1,0,0,1,0,1,0,1,0,1,0,1,0)
(1,0,1,1,1,1,1,0,0,1,1,1,1,0,0)
(1,1,1,0,1,1,1,1,0,1,0,1,0,1,0)
(1,0,1,0,1,0,1,1,1,0,1,0,0,0,1)
(0,1,0,1,0,1,0,0,0,1,1,0,0,1,0)->出口
2.程序说明及任务:
迷宫问题要求寻找一条从入口到出口的路径。
路径是由一组位置构成的,每个位置上都没有障碍,且每个位置(第一个除外)都是前一个位置的东、南、西或北的邻居,如图C。
计算机走迷宫的方法是,采取一步一步试探的方法。
每一步都从东开始,按顺时针对8个方向进行试探,若某方向上maze(x,y)=0,表示可以通行,则走一步;若maze(x,y)=1,表示不可以通行,须换方向再试,直到8个方向都试过;若maze(x,y)均为1,说明此步已无路可走,需退回一步,在上一步的下一个方向重新开始探测。
为此,需设置一个栈,用于记录所走过的位置和方向(i,j,dir)。
当退回一步时,从栈中退出一个元素,以便在上一个位置的下一个方向上探测,如又找到一个行进方向,则把当前位置和方向重新进栈,并走到新的位置。
若探测到位置(m,n),则已经到达迷宫的出口,可以停止探测,输出存在栈中的路径;如果在某一位置的8个方向上堵塞,则退回一步,继续探测;如果已退到迷宫的入口(栈中无元素),则表示此迷宫无路径可走。
二、概要设计
主要思想:
1.用矩阵表示的迷宫;
2.将矩阵表示的迷宫转换成无向图,用邻接表存储;
3.对无向图从入口结点开始广度优先搜索;
4.用一个一维数组存储各个结点的前驱结点的编号;
5.通过出口结点Vn找到其前驱结点Vn-1,再通过Vn-1找到Vn-2;
6.依次类推直到找到出口结点。
基本设计算法:
1.设置数组maze[MAX][MAX]来模拟迷宫,
2.maze[i][j]=0表示该方格所在的位置可通行,A[i][j]=1则表明该位置不能通行;
3.定义无向图G,迷宫的规格(行、列)存放在G.rownum、G.colnum中,其结点数同迷宫(数组maze[MAX][MAX])的方格个数。
4.每一个结点的邻接结点为其相邻(从点在数组maze[][]中所处的位置的角度)的八个点中值为0的点,按结点的顺序依次找出每一个点的邻接点,此即完成迷宫图的数组表示转化为无向图表示,G用邻接表存储;
5.采用图的广度优先遍历的方法,从入口结点开始遍历,直到碰到出口结点为止。
并设置record数组来记录结点i在广度优先遍历过程中的前驱结点的编号record[i];
6.这样(record[i],i)表示存在从结点record[i]到i的边,这样就可以从出口顶点在图中的编号回溯出口顶点,如此,一条从入口到出口的最短路径就找到了。
在定义record数组是将所有初始值设为-1,只是为了判断是否存在从入口到出口的路径,因为如果出口结点i的record[i]值为-1则表明遍历过程没有找到出口,也就是说此迷宫无解.
7.反之record[i]!
=-1,则此迷宫一定是有解的,因为只有遍历过程中找到了出口I,才会改变record[i]的值,而这个改变后的值是不可能为-1的;
8.输出从入口到出口的路径,用回溯法,只需将图中结点的编号换算成数组maze的坐标即可。
三、详细设计
1.基本过程的算法:
设定a[0][0]为入口位置;
do{
若当前位置可通,
则{将当前位置插入栈顶;
若该位置是出口位置,则结束;
否则切换当前位置的东邻方块为新的当前位置;
}
否则,
若栈不空且栈顶位置尚有其他方向未经探索,
则设定新的当前位置为沿顺时针方向旋转找到的栈顶位置的下一相邻块;
若栈不空但栈顶位置的四周均不可通,
则{删栈顶位置;
若栈不空,则重新测试新的栈顶位置,
直到找到一个可通的相邻块或出栈至栈空;
}
}while(栈不空);#include
2.函数的调用关系图
迷宫存储从A[0][0]开始,包括记录路径的数组record[]也是从0下标开始纪录的,为了使输入和输出的坐标符合习惯,只在输入输出时作了一些改变。
迷宫主要部分转换图示矩阵转化成图的邻接表存储:
[i](G.vexs[i].data,G.vexs[i].first)→(adjno,next)
[1](0,first)→(11,NULL)
[2](1,NULL)
[3](1,NULL)
……
[11](0,first)→(21,next)→(1,NULL)
……
[59](0,first)→(60,next)→(49,NULL)
[60](0,first)→(59,NULL)
以入口结点作广度优先遍历的起始结点,知道找到出口结点结束遍历,并用record[i]记录遍历的结点i的前驱结点record[i],然后用回溯法输出路径(路径换算成坐标形式)即可。
3.抽象数据类型定义描述
①ADTTis
Data
当前位置的行坐标、当前位置的列坐标、走到下一位置的方向
endADTT
②ADTLinknodeis
Data
数据域、指针域
Operation
Linknode//构造函数
用于构造结点
endADTLinkNode
③ADTStackis
Data
栈顶指针
Operation
Stack//构造函数
输入:
无
初始化栈:
置空栈
~stack//析构函数
Push
输入:
要进栈的项e
前置条件:
无
动作:
把e压入栈顶
输出:
无
后置条件:
栈顶增加了一个新结点,栈顶指针指向新结点
Pop
输入:
无
前置条件:
栈非空
动作:
弹出栈顶元素
输出:
返回栈顶元素的值
后置条件:
删除栈顶元素
GetPop
动作:
返回栈顶元素的值
Clear
动作:
清空栈
empty
动作:
检查栈顶指示是否等于NULL
输出:
栈空时返回1,否则返回0
endADTStack
四、调试分析
1)程序将用到的函数及参数:
1.迷宫输入:
adjlistinput(intmaze[][MAX]);
2.迷宫图的图结构(邻接表存储)化:
adjlistconvert(intmaze[][MAX],adjlistG);
3.对图的广度优先遍历:
voidtravgraph(adjlistG,intrecord[],intentry,intexit);
4.迷宫的解的输出:
voidoutput(adjlistG,intrecord[],intentry,intexit)。
2)算法分析和改进
在程序的设计中,总的调试比较顺利。
在找出一些小问题顺利编译成功以后,在测试结果的时候发现程序运行的结果错误。
经过仔细的检查,发现程序中8个if语句中的算法出现错误。
导致运行时搜索的方向发生错误。
在if语句中的”i-1=0&&j+1经过测试结果,结果正确。
虽然程序能够编译成功,但是还有2个警告项存在.winTC提示警告:
success.c126:
不可移动的指针(地址常数)转换在travgraph函数中。
success.c132:
不可移动的指针(地址常数)转换在travgraph函数中。
经过改进,问题成功解决。
警告消失。
对于不能显示汉语的TC,最后将中文转换为英文,唯一不足的是(*,*)在显示的时候是乱码。
未能几时改进。
3)体会:
要能很好的掌握编程,仅仅通过几个简单的程序的编写是无法达成的,更需要大量积累和深入才有可能。
就从这个迷宫的问题来说,在迷宫图向图结构的转化时,对图可用广度优先搜索的算法来解决两点间路径问题。
在程序的编写中也不能一味得向已有的程序进行模仿,而要自己去摸索,去寻求最好的解决方式,只有带着问题去反复进行实践,才能更熟练的掌握和运用,当然,对现有的程序也要多去接触,因为有些程序是我们无法在短时间内想出来的。
最重要的一点就是要持之以恒,要经常性的复习原来所接触的程序,这样才能保证我们有足够的经验去面对程序问题。
五、测试结果:
1.运行出现“Pleaseinputthelengthofthemaze”字样,“rownum”表示输入”行数”“colnum”表示输入”列数”。
2.然后会出现“Inputthe1row”这时输入数组的第一行,后面的依次类推。
3.数组输入完毕后,会出现”Inputtingthemazeentrancesitsthemark(*,*)”
即输入起点坐标。
然后会出现”Inputtingthemazeexportstositthemark(*,*)”要求输入出口坐标。
4.点击回车运行所求的路径即会显示出来。
所示输入迷宫大小:
行数(<=15):
10[↙]
列数(<=15):
15[↙]
0表示可通行,1表示不能通过。
输入第1行:
[1]0 0 0 1 0 0 0 1 0 0 0 1 0 0 1
输入第2行:
[2]0 1 0 0 0 1 0 1 0 0 0 1 1 1 1
输入第3行:
[3]0 1 1 1 1 1 0 1 0 0 1 1 1 0 1
输入第4行:
[4]1 1 0 0 0 1 1 0 1 1 0 0 1 0 1
输入第5行:
[5]1 0 0 1 0 1 1 1 1 0 1 0 1 0 1
输入第6行:
[6]1 0 1 0 0 1 0 1 0 1 0 1 0 1 0
输入第7行:
[7]1 0 1 1 1 1 1 0 0 1 1 1 1 0 0
输入第8行:
[8]1 1 1 0 1 1 1 1 0 1 0 1 0 1 0
输入第9行:
[9]1 0 1 0 1 0 1 1 1 0 1 0 0 0 1
输入第10行:
[10]0 1 0 1 0 1 0 0 0 1 1 0 0 1 0
输入入口坐标(*,*):
1,1
输入出口坐标(*,*):
10,15
附录
1.存储结构:
邻接表存储存储
2.基本过程的算法:
(1).栈初始化;
(2).将入口点坐标及到达该点的方向(设为-1)入栈;
(3).while(栈不空时)
{栈顶元素元素(x,y,d)出栈;
求出下一个要试探的方向d++;
while(还有剩余试探方向时)
{if(d方向可走)
则{(x,y,d)入栈;
求新点坐标(i,j);
将新点(i,j)切换为当前点(x,y);
if((x,y)==(n,n))结束;
elsed=0;
}
elsed++;
}
}
3.源程序:
#include
#defineMAX15
#defineNULL0
typedefstructlistnode
{intadjno;
structlistnode*next;
}listnode;
typedefstruct
{intdata;
listnode*first;
}headnode;
typedefstruct
{headnodevexs[MAX*MAX];
intvexnum;
intrownum;
intcolnum;
}adjlist;
adjlistG;
/*输入迷宫,0为可通行,1为不可通行,用二维矩阵表示*/
adjlistinput(intmaze[][MAX],adjlistG)
{inti,j;
intrownum,colnum;
printf("Pleaseinputthelengthofthemaze:
\n");
printf("rownum=");
scanf("%d",&G.rownum);
printf("colnum=");
scanf("%d",&G.colnum);
for(i=0;i{printf("Inputthe%drow:
[%d]",i+1,i+1);
for(j=0;jscanf("%d",&maze[i][j]);
}
return(G);
}
/*二维数组向无向图的转化*/
adjlistchange(intmaze[][MAX],adjlistG)
{inti,j;
listnode*p;
G.vexnum=G.rownum*G.colnum;
for(i=0;i{G.vexs[i].data=maze[i/G.colnum][i%G.colnum];
G.vexs[i].first=NULL;
}
for(i=0;ifor(j=0;j{if(i-1>=0&&maze[i-1][j]==0)
{p=(listnode*)malloc(sizeof(listnode));
p->adjno=(i-1)*G.colnum+j;
p->next=G.vexs[i*G.colnum+j].first;
G.vexs[i*G.colnum+j].first=p;
}
if(i+1{p=(listnode*)malloc(sizeof(listnode));
p->adjno=(i+1)*G.colnum+j;
p->next=G.vexs[i*G.colnum+j].first;
G.vexs[i*G.colnum+j].first=p;
}
if(j-1>=0&&maze[i][j-1]==0)
{p=(listnode*)malloc(sizeof(listnode));
p->adjno=i*G.colnum+j-1;
p->next=G.vexs[i*G.colnum+j].first;
G.vexs[i*G.colnum+j].first=p;
}
if(j+1{p=(listnode*)malloc(sizeof(listnode));
p->adjno=i*G.colnum+j+1;
p->next=G.vexs[i*G.colnum+j].first;
G.vexs[i*G.colnum+j].first=p;
}
if(i-1>=0&&j-1>=0&&maze[i-1][j-1]==0)
{p=(listnode*)malloc(sizeof(listnode));
p->adjno=(i-1)*G.colnum+j-1;
p->next=G.vexs[i*G.colnum+j].first;
G.vexs[i*G.colnum+j].first=p;
}
if(i-1>=0&&j+1{p=(listnode*)malloc(sizeof(listnode));
p->adjno=(i-1)*G.colnum+j+1;
p->next=G.vexs[i*G.colnum+j].first;
G.vexs[i*G.colnum+j].first=p;
}
if(i+1=0&&maze[i+1][j-1]==0)
{p=(listnode*)malloc(sizeof(listnode));
p->adjno=(i+1)*G.colnum+j-1;
p->next=G.vexs[i*G.colnum+j].first;
G.vexs[i*G.colnum+j].first=p;
}
if(i+1{p=(listnode*)malloc(sizeof(listnode));
p->adjno=(i+1)*G.colnum+j+1;
p->next=G.vexs[i*G.colnum+j].first;
G.vexs[i*G.colnum+j].first=p;
}
}
return(G);
}
/*用inttravgraph()函数广度优先遍历无向图*/
inttravgraph(adjlistG,intrecord[],intentry,intexit)
{listnode*p;
intqueue[MAX*MAX],visited[MAX*MAX];/*用visited[]数组标记图的结点是否遍历过*/
inti;
intfront=0,rear=1;
for(i=0;i{record[i]=-1;/*置record[i]为-1,以便检查从入口到出口结点V[n]有无通路*/
visited[i]=0;
}
visited[entry]=1;
queue[rear]=entry;
while(front!
=rear)/*记录遍历路径,(record[i],i)表示存在从结点record[i]到i的边*/
{p=G.vexs[queue[front+1]].first;
while(p!
=NULL)
{if(visited[p->adjno]==0)
{visited[p->adjno]=1;
record[p->adjno]=queue[front+1];
queue[++rear]=p->adjno;
if(p->adjno==exit)
gotoend;
}
p=p->next;
}
front=front+1;
}
end:
;
}
/*用回溯法找到从出口到入口的路径,并输出*/
voidoutput(intrecord[],intentry,intexit)
{inti=0,t;
if(entry==exit&&G.vexs[exit].data==0)
printf("It'salreadyatexport\n");
else
if(record[exit]!
=-1)
{t=exit;
{printf("Themostshortpathisasfollows:
\n");
while(t!
=entry)
{printf("[%d,%d]<-",t/G.colnum+1,t%G.colnum+1);
t=record[t];
if(++i%5==0)printf("\n");
}
printf("[%d,%d]",t/G.colnum+1,t%G.colnum+1);
}
}
else
printf("Havenopathfromtheentrancetotheexit\n");
}
voidmain()
{intentry_row,entry_col,exit_row,exit_col,entry,exit;
intmaze[MAX][MAX];
intrecord[MAX*MAX];
G=input(maze,G);
G=change(maze,G);
printf("Inputtingthemazeentrancesitsthemark(*,*):
");
scanf("%d,%d",&entry_row,&entry_col);
printf("Inputtingthemazeexportstositthemark(*,*):
");
scanf("%d,%d",&exit_row,&exit_col);
entry=--entry_row*G.colnum+--entry_col;
exit=--exit_row*G.colnum+--exit_col;
travgraph(G,record,entry,exit);
output(record,entry,exit);
getch();
}
4.测试数据和结果
输入:
行数(<=15):
10[↙]
列数(<=15):
15[↙]
0表示可通行,1表示不能通过。
输入第1行:
[1]0 0 0 1 0 0 0 1 0 0 0 1 0 0 1
输入第2行:
[2]0 1 0 0 0 1 0 1 0 0 0 1 1 1 1
输入第3行:
[3]0 1 1 1 1 1 0 1 0 0 1 1 1 0 1
输入第4行:
[4]1 1 0 0 0 1 1 0 1 1 0 0 1 0 1
输入第5行:
[5]1 0 0 1 0 1 1 1 1 0 1 0 1 0 1
输入第6行:
[6]1 0 1 0 0 1 0 1 0 1 0 1 0 1 0
输入第7行:
[7]1 0 1 1 1 1 1 0 0 1 1 1 1 0 0
输入第8行:
[8]1 1 1 0 1 1 1 1 0 1 0 1 0 1 0
输入第9行:
[9]1 0 1 0 1 0 1 1 1 0 1 0 0 0 1
输入第10行:
[10]0 1 0 1 0 1 0 0 0 1 1 0 0 1 0
输入入口坐标(*,*):
1,1
输入出口坐标(*,*):
10,15
结果:
Thepathisasfollow:
[10,15]<-[9,14]<-[8,15]<-[7,14]<-[6,13]<-[5,12]