算法分析与设计查找迷宫的最短路径深度算法.docx
《算法分析与设计查找迷宫的最短路径深度算法.docx》由会员分享,可在线阅读,更多相关《算法分析与设计查找迷宫的最短路径深度算法.docx(17页珍藏版)》请在冰豆网上搜索。
算法分析与设计查找迷宫的最短路径深度算法
算法分析与设计
查找迷宫的最短途径(深度算法)
计算机科学与技术12级
16班
2021/12/16
【摘要】:
迷宫求解是一个古老的游戏,要在迷宫中找到出口,需要经过一连串的错误尝试才能找到正确的途径,有的时候甚至找不到途径。
类似于给定一个m*n的矩形网格,设其左上角为起点S。
一辆汽车从起点出发驶向右下角终点T。
在假设干网格处设置了障碍,表示该网格不可到达。
设计一个算法,求汽车从起点S出发到达终点T的一条道路。
用计算机求解这个问题时,我们通常采用的是回溯方法,即从入口出发,顺某方向向前探究,假设能走通,那么继续往前走;否那么沿原路退回。
换一个方向再继续探究,直至所有可能的通路都探究到为止。
为了保证在任何位置上都能沿原路退回,显然需要用一个后进先出的构造来保存从入口到当前位置的途径。
因此,在求迷宫通路的算法中应用“栈〞也就是自然而然的事。
当然还有其他的方法来解决,例如顺序表,深度优先遍历,广度优先遍历等。
【关键词】:
最短途径;时间复杂度;深度优先搜索
【Summary】Mazesolvingisanancientgame,youwanttofindtheexitinthemaze,needtogothroughaseriesoftrialanderrortofindtherightpath,andsometimesnotevenfindthepath.Am*nrectangulargrid,similartoagivensetitsupper-leftcornerasthestartingpointS.AcarfromthestartingpointtowardsthebottomrightcorneroftheendofT.Setupbarriersatcertaingrid,indicatesthatthegridisunreachable.Designanalgorithm,findthecarstartingtoreachtheendpointT,routefromthestartingpointS.Usethecomputertosolvethisproblem,weusuallyusethebacktrackingmethod,thatis,startingfromtheentrance,Shunforwardtoexploreadirection,ifwegothrough,andcontinuetomoveforward;otherwisereturnalongthesameroute.Continuetoexploreanotherdirection,untilallpossiblepathstoexploretofar.Inordertoensurethatinanypositionalongthesamerouteback,itisclearthattheneedtouseaLIFOstructuretosavethepathfromtheentrancetothecurrentposition.Therefore,inseekinglabyrinthpathalgorithmapplication"stack"isthenaturalthingtodo.Ofcourse,thereareotherwaystosolve,forexample,thesequencetable,depth-firsttraversal,breadth-firsttraversal.
【Keyphrase】Shortestpath;timecomplexity;deep-firstsearch
摘要1
关键字1
Summary1
一、问题描绘3
二、算法分析3
三、设计方案3
1总体方案3
2主要设计思路3
四、时间复杂度6
五、总结6
六、参考文献7
七、附录7
一、
问题描绘
迷宫最短途径(theshortestpathofmaze)问题是一个典型的搜索、遍历问题,其程序设计想在人工智能设计、机器人设计等事务中均有应用。
如图所示,一个N×M的大方块迷宫,带斜线的单元格表示墙壁(障碍),空白的单元格表示通道。
迷宫问题可以表述为:
寻找从某一个给定的起始单元格(迷宫入口)出发,经由行相邻或列相邻的通道单元格,最终可以到达目的单元格(迷宫出口),所走过的单元格序列。
行进中,只能沿上下左右四个方向前进。
而迷宫最短途径问题就是找出从迷宫入口到出口所经过单元格个数最少的途径。
二、算法分析
从入口出发,顺着某一方向向前探究,假设能走通,那么继续往前走;否那么沿原路退回(回溯),换一个方向再继续探究,直至所有可能的通路都探究到为止。
假设恰好某一步探究到出口,那么就找到了从入口到出口的途径。
为了保证在任何位置上都能沿原路退回,防止死循环,需要使用堆栈来保存大量记录。
而要求解迷宫最短途径,那么必须用深度优先搜索出所有到达出口的途径,通过比较得到最短间隔的途径,这样也必然要求增加数据空间来保存搜索过程中当前最短途径,增加了空间复杂度。
三、设计方案
1总体方案
走迷宫问题的走迷宫的过程可以模拟为一个搜索的过程:
每到一处,总让它按东、南、西、北、4个方向顺序试探下一个位置;假设某方向可以通过,并且不曾到达,那么前进一步,在新位置上继续进展搜索;假设4个方向都走不通或曾经到达过,那么退回一步,在原来的位置上继续试探下一位置。
每前进或后退一步,都要进展判断:
假设前进到了出口处,那么说明找到了一条通路;假设退回到了入口处,那么说明不存在通路。
2主要设计思路
用一个字符类型的二维数组表示迷宫,输入值的范围是0,1,其中0表示途径,1为非途径(即障碍),输入的矩阵大小和矩阵的内容是靠手动输入。
设计一个模拟走迷宫的算法,为其寻找一条从入口点到出口点的通路。
解决迷宫问题,面对的第一个问题是迷宫的表示方法。
假定用n*m矩阵来描绘迷宫。
左上角为入口,右下角为出口。
n和m分别表示迷宫的行数和列数。
矩阵中,0表示可通行的途径,1代表障碍。
如图1-1表示4*6的迷宫矩阵表示。
0
1
1
0
0
0
0
0
0
1
0
0
0
0
0
0
0
0
0
1
1
0
0
0
图1-1迷宫问题的矩阵表示
假设如今的位置〔i,j〕,那么下一步有:
上、下、左、右四种走法,如图1-2表示。
(i-1,j)
(i,j-1)
〔i,j〕
(i,j+1)
(i+1,j)
图1-2四种可能的挪动方向
迷宫内部的位置有四种挪动的位置:
上、下、左、右。
而迷宫的边界位置有两种或三种可能的挪动。
为防止在处理内部和边界位置是存在差异,可在迷宫的周围增加一圈障碍物,如图1-3表示。
1
1
1
1
1
1
1
1
1
0
1
1
0
0
0
1
1
0
0
0
1
0
0
1
1
0
0
0
0
0
0
1
1
0
1
1
0
0
0
1
1
1
1
1
1
1
1
1
图1-3为迷宫增加一圈障碍物
增加一圈障碍物之后,原迷宫中的每个位置都可以有四种挪动方向。
而且可以使程序不必去处理边界条件,从而大大简化代码的设计。
这种简化是以稍稍增加数组amze的空间为代价的。
分别用x和y来指定每个迷宫位置的行坐标和列坐标。
可以定义一个类classT来表示迷宫的位置。
classT//定义描绘迷宫中当前位置的构造类型
{
public:
intx;//x代表当前位置的行坐标
inty;//y代表当前位置的列坐标
intdir;//0:
无效,1:
右,2:
下,3:
左,4:
上
};
当输入一个二维数组时,每次输入的元素都存放在一个栈里面。
所以定义一个栈Stack用于存放二维数组元素。
这里用到栈,栈是限定尽在表位进展插入和删除的线性表。
对于栈来说,允许进展插入和删除的一段,称为栈顶;不允许插入和删除的另一端,那么成为栈底。
在这个问题中主要运用了栈的各种根本操作,例如构造空栈,判断栈是否为空,入栈操作,出栈操作等等。
这里是栈的一个重要应用,就是实现递归。
classStack
{public:
Stack();//构造函数,置空栈
~Stack();//析构函数
voidPush(Te);//把元素data压入栈中
TPop();//使栈顶元素出栈
TGetPop();//取出栈顶元素
voidClear();//把栈清空
boolempty();//判断栈是否为空,假设为空那么返回1,否那么返回0
private:
LinkNode*top;};//指向第一个结点的栈顶指针
调试结果如图1-4:
图1-4迷宫和矩阵的建立
假设位置〔i,j〕的下一步的四个方向都是可以走的,假设出发的先后顺序是向上.向下.向左.向右。
每个结点都是按照先后顺序来决定下一个结点的方向。
一旦做出了决定,就需要知道下一个位置的坐标。
可通过保存一个如图1-5所示的偏移量表来计算这些坐标。
可以把向右.向左.向下.向上挪动分别编号为0.1.2.3。
在图1-3中,move[i].x和move[i].y分别给出了从当前位置沿方向挪动到下一个相连位置时,x和y坐标的增量。
方向索引值〔dir〕
move[dir].x
move[dir].x
方向
0
0
1
右
1
1
0
下
2
0
-1
左
3
-1
0
上
图1-5偏移量表
因此假设如今的位置是〔2,3〕,那么其右边相连位置坐标的行坐标为2+move[0].x=2,列坐标为3+move[0].y=4.
定义一个主函数intmain(),用于调用其他函数实现函数的嵌套调用,实现整个程序。
这里运用的二维指针我是参考书上的。
intmain()
{
intm=0,n=0;//定义迷宫的长和宽
int**maze;//定义二维指针存取迷宫
maze=GetMaze(m,n);//调用GetMaze(int&m,int&n)函数,得到迷宫
if(Mazepath(maze,m,n))//调用Mazepath(int**maze,intm,intn)函数获取途径
cout<<"迷宫途径探究成功!
\n";
elsecout<<"途径不存在!
\n";
return0;}
调试结果:
图1-6搜索迷宫完成界面
四、时间算法复杂度:
O(n²〕〔不确定,我不太会算这个。
〕
对相关问题的考虑:
这个迷宫问题的算法中,要在开始设置迷宫的大小。
在探究迷宫道路的过程中,是通过不断的尝试来得到最终的结果,由于不能对已经设定为可走途径进展返回,所以在这个算法中有时候可能不能得到走出迷宫的途径。
五、总结
本次设计进展比较顺利,如期完成,并且到达了预先的设计要求,完全贯彻和执行了设计的总体方案。
对于迷宫求解的模拟实现比较成功。
然而,限于时间和程度,这个设计还有很多的缺乏之处。
迷宫问题是一个古老的问题,要在迷宫中找到出口,需要经过一连串的的错误尝试才能找到正确的途径,有时甚至找不到途径。
而且这里有很多方法可以完成迷宫问题,例如顺序表,深度优先遍历,广度优先遍历等,但是我写不出程序。
于是参考了?
数据构造?
书和我以前的一些设计,所以我们这里用的是栈。
在这个问题中主要运用了栈的各种根本操作,例如构造空栈,判断栈是否为空,入栈操作,出栈操作等等。
通过这次的作业,我又学会了一种解决迷宫问题的方法,我很快乐。
当让在完成设计过程中,我也遇到了很多难题,比方用什么方法解决问题,怎样创立调用栈等等,但在再次复习了当时所学的?
C++?
?
数据构造?
等课程后,发现这些问题还是可以解决的,而且解决的方法不止一种。
在这里我参考的最多的是?
数据构造?
中“栈的应用〞那一节的知识,它给我了很大帮助。
本程序虽然简短,但是却有很多难点出现。
首先是对基类的调试。
为了减少调试的难度,我先调试了一下类的程序。
刚开始是在主函数里面直接对程序赋值,发现这将大大限制程序的可重用性,而且无法灵敏运用。
在指导老师的帮助下,我将程序改成用键盘直接输出,这样的话方便实现。
当然,在我遇到苦难时候,老师和同学的帮助也是很大的。
他们使我进步。
也让我体会到了成功的来之不易,只有真正付出过才有满意的收获。
在此,我诚心的对所有帮助过我的老师、学长、同学们说一句:
谢谢!
六、参考文献
[1]严蔚敏,吴伟民.数据构造[M].北京:
清华大学出版社,2002.
[2]陈媛.算法与数据构造[M].北京:
清华大学出版社,2005.
[3]苏德富.计算机算法设计与分析[M].北京:
电子工业出版社,2005.
七、附录:
#include
usingnamespacestd;
classT//定义描绘迷宫中当前位置的构造类型
{
public:
intx;//x代表当前位置的行坐标
inty;//y代表当前位置的列坐标
intdir;//0:
无效,1:
右,2:
下,3:
左,4:
上
};
classLinkNode//链表结点
{
friendclassStack;
public:
Tdata;
LinkNode*next;
};
classStack
{
public:
Stack();//构造函数,置空栈
~Stack();//析构函数
voidPush(Te);//把元素data压入栈中
TPop();//使栈顶元素出栈
TGetPop();//取出栈顶元素
boolempty();//判断栈是否为空,假设为空那么返回1,否那么返回0
private:
LinkNode*top;//指向第一个结点的栈顶指针
};
Stack:
:
Stack()//构造函数,置空栈
{
top=NULL;
}
Stack:
:
~Stack()//析构函数
{
}
voidStack:
:
Push(Te)//把元素x压入栈中
{
LinkNode*P;
P=newLinkNode;
P->data=e;
P->next=top;
top=P;
}
TStack:
:
Pop()//使栈顶元素出栈
{
TTemp;
LinkNode*P;
P=top;
top=top->next;
Temp=P->data;
deleteP;
returnTemp;
}
TStack:
:
GetPop()//取出栈顶元素
{
returntop->data;
}
boolStack:
:
empty()//判断栈是否为空,假设为空那么返回1,否那么返回0
{
if(top==NULL)return1;
elsereturn0;
}
intmove[4][2]={{0,1},{1,0},{0,-1},{-1,0}};//定义当前位置挪动的4个方向
boolMazepath(int**maze,intm,intn);
//寻找迷宫maze中从〔0,0〕到〔m,n〕的途径
//到那么返回true,否那么返回false
voidPrintPath(Stackp);//输出迷宫的途径
int**GetMaze(int&m,int&n);//获取迷宫
//返回存取迷宫的二维指针
intmain()
{
intm=0,n=0;//定义迷宫的长和宽
int**maze;
maze=GetMaze(m,n);//调用GetMaze(int&m,int&n)函数,得到迷宫
if(Mazepath(maze,m,n))//调用Mazepath(int**maze,intm,intn)函数获取途径
cout<<"迷宫途径探究成功!
\n";
elsecout<<"途径不存在!
\n";
return0;
}
int**GetMaze(int&m,int&n)//返回存取迷宫的二维指针
{
int**maze;//定义二维指针存取迷宫
inti=0,j=0;
cout<<"请输入迷宫的长和宽:
";
inta,b;cin>>a>>b;//输入迷宫的长和宽
cout<<"请输入迷宫内容:
\n";
m=a;
n=b;//m,n分别代表迷宫的行数和列数
maze=newint*[m+2];//申请长度等于行数加2的二级指针
for(i=0;i{
maze[i]=newint[n+2];
}
for(i=1;i<=m;i++)//输入迷宫的内容,1代表不通,0代表可通
for(j=1;j<=n;j++)
cin>>maze[i][j];
for(i=0;imaze[i][0]=maze[i][n+1]=1;
for(i=0;imaze[0][i]=maze[m+1][i]=1;
returnmaze;//返回存贮迷宫的二维指针maze
};
boolMazepath(int**maze,intm,intn)//寻找迷宫maze中从〔0,0〕到〔m,n〕的途径
//到那么返回true,否那么返回false
{
Stackq,p;//定义栈p、q,分别存探究迷宫的过程和存储途径
TTemp1,Temp2;
intx,y,loop;
Temp1.x=1;
Temp1.y=1;
(Temp1);//将入口位置入栈
(Temp1);
maze[1][1]=-1;//标志入口位置已到达过
while(!
())//栈q非空,那么反复探究
{
Temp2=();//获取栈顶元素
if(!
(().x==().x&&().y==().y))
(Temp2);
//假设有新位置入栈,那么把上一个探究的位置存入栈p
for(loop=0;loop<4;loop++)//探究当前位置的4个相邻位置
{
x=Temp2.x+move[loop][0];//计算出新位置x位置值
y=Temp2.y+move[loop][1];//计算出新位置y位置值
if(maze[x][y]==0)//判断新位置是否可达
{
Temp1.x=x;
Temp1.y=y;
maze[x][y]=-1;//标志新位置已到达过
(Temp1);//新位置入栈
}
if((x==(m))&&(y==(n)))//成功到达出口
{
Temp1.x=m;
Temp1.y=n;
Temp1.dir=0;
(Temp1);//把最后一个位置入栈
PrintPath(p);//输出途径
returntrue;//表示成功找到途径
}
}
if(().x==().x&&().y==().y)
//假设没有新位置入栈,那么返回到上一个位置
{
();
();
}
}
returnfalse;//表示查找失败,即迷宫无路经
}
voidPrintPath(Stackp)//输出途径
{
cout<<"迷宫的途径为\n";
cout<<"括号内的内容分别表示为(行坐标,列坐标,方向)\n";
Stackt;//定义一个栈,按从入口到出口存取途径
inta,b;
Tdata;
LinkNode*temp;
temp=newLinkNode;//申请空间
temp->data=();//取栈p的顶点元素,即第一个位置
(temp->data);//第一个位置入栈t
deletetemp;//释放空间
while(!
())//栈p非空,那么反复转移
{
temp=newLinkNode;
temp->data=();//获取下一个位置
//得到行走方向
a=().x-temp->;//行坐标方向
b=().y-temp->;//列坐标方向
if(a==1)temp->=1;//方向向下,用1表示
elseif(b==1)temp->=2;//方向向右,用2表示
elseif(a==-1)temp->=3;//方向向上,用3表示
elseif(b==-1)temp->=4;//方向向左,用4表示
(temp->data);//把新位置入栈
deletetemp;
}
//输出途径,包括行坐标,列坐标,下一个位置方向
while(!
())//栈非空,继续输出
{
data=();
cout<<'('<<<<','<<<<",";//输出行坐标,列坐标
switch()//输出相应的方向
{
case1:
cout<<"下)\n";break;
case2:
cout<<"右)\n";break;
case3:
cout<<"上)\n";break;
case4:
cout<<"左)\n";break;
case0:
cout<<")\n";break;
}}}