Astar算法详解.docx
《Astar算法详解.docx》由会员分享,可在线阅读,更多相关《Astar算法详解.docx(17页珍藏版)》请在冰豆网上搜索。
Astar算法详解
A*算法详解
今天刚开通了XX博客,这也是我在这里的第一篇文章,以后会在此写下我学习AS3的一些心得和技巧,方便自己日后的复习也让一些新手门能快速的入门,那么,开门见山,第一篇就写AS3版的A*算法吧。
那么,我个人习惯不喜欢把简单的东西往复杂了搞,虽然复杂代表着规范,和严谨,但是处于学习的目的,我给出的A*算法,相当的简单和实用,当然重点是算法思想,下面就跟随我的指示太了解A*算法吧。
那么什么是A*,其实就是在很多障碍物的地图上,物体从A点移动到B点的路径,当然,光要路径也不对,我们要找的就是其中最短的一条,这样问题就来了,如何才能找到最短的一条呢?
首先,我们得有一个标准来验证。
我们需要一个探路器和节点,比如我们每走一步都会用探路器在我们周围的8个点上放上节点,然后每个节点会返回出他们的代价,我们根据探路器的指示,找到代价最少的节点,然后下一步就走到了那里,然后继续上面的操作,那么,原理是很简单的,我们先需要一个节点类(node),节点类要做的事情就是返回出代价,至于怎么返回请看下面:
假设我们在地图上每走一步都需要消耗代价,比如直线走一格是10代价,斜线走是14代价,那么我们可以根据以下的公式计算代价:
F=H+G
总代价=目标到当前的直线代价+从开始节点到当前节点的移动代价
那么,其实看到这里我们就只要,需要3个函数,F函数和H函数还有G函数,那么可以写成:
functionF():
int{
H()+G();
}
好了,到了这里,A*算法我们已经完成一半了,现在只要去实现H和G了,那么继续看:
publicfunctiongetH():
int{
returnMath.abs(h-Goal_node.h)*10+Math.abs(l-Goal_node.l)*10;
//返回目标到当前的直线代价
}
哇,好少了啊,对,就一行,H计算的是目标节点到当前节点所消耗的代价,注意了:
只需要计算行和列,斜线的不算,当然不能少了取绝对值,我们不需要负数,之前我们说了直线的代价是10,所以我们把他们的代价都*上10,当然你可以根据自己需求来*上不同的代价,OK,H搞定了,下面来看看G:
publicfunctiongetG():
int{
if(Math.abs(h-Start_node.h)==1&&Math.abs(l-Start_node.l)==1){
return14+Father_int;
//返回父节点的带价值和当前节点到开始节点值等于一路走过来的带价值
}else{
returnMath.abs(h-Start_node.h)*10+Math.abs(l-Start_node.l)*10+Father_int;
//同上
}
}
//返回从开始节点到当前节点的移动代价
//G函数比H函数多了一点,就是计算斜角的代价,但是,记住,这个斜角只是当前节点的左上,左下,右上,右下的那一格,那么如何来计算是否是斜角呢?
其实只要计算当前行减去父节点和列减去父节点的列是否都等于1,这里也一样需要取绝对值,如果都为1则说明不在同一行也不在同一列,Father_int是什么呢?
是父节点的代价,G函数计算的是当前的代价还要加上父节点的代价,才能算出开始节点到当前节点的总代价,到了这里A*逻辑部分已经完成一大半了,最后我们还需要将找到的路径保存进一个数组里,看下面代码:
publicfunctiongetPath():
Array{
if(Father_node==this){
varccc:
Array=newArray();
ccc=[[h,l]];
returnccc;
}else{
varaa:
Array=Father_node.getPath();
varanswer:
Array=aa.slice(0,aa.length);
answer[aa.length]=[h,l];
returnanswer;
}
}
太少了,对,就是这么简单啦,首先我们检测目标节点的父节点是否等于自己,等于自己说明达到了起点,因为起点是没有父节点的,所以我们就设为是自己,好,先就此打住,看看下面的else,这里我们用了递归来寻找节点,首先我们新建立一个数组等于父节点返回的节点,然后在把它拷贝一份进新的输入,因为FLASH的数组是动态的,所以我们直接在尾部answer[aa.length]添加进当前节点的行和列,最后返回,直到返回到父节点也把自己的坐标返回了,整个递归调用结束,最后我们需要知道我们是否已经找到目标了,其实我们就可以直接判断当前的节点是否是目标节点:
publicfunctionequals(me:
Object):
Boolean{
if(meisnode){
if(me.h==h&&me.l==l){
returntrue;
}else{
returnfalse;
}
}else{
returnfalse;
}
}
对啊,就是判断行和列就OK啦,这个方法如果返回真了就是找到了,如果返回假就说明没找到,
自此整个节点核心算法完毕,
下面是整个节点类:
package{
publicclassnode{
publicvarh:
int;
publicvarl:
int;
publicvarFather_node:
node;
publicvarGoal_node:
node;
publicvarStart_node:
node;
publicvarFather_int:
int;
publicfunctionnode(h:
int,l:
int,Father_node:
node,Goal_node:
node,Start_node:
node){
this.h=h;
//接受行
this.l=l;
//接受列
this.Father_node=Father_node;
//接受父类节点
this.Goal_node=Goal_node;
//接受目标节点
this.Start_node=this.Father_node;
//接受开启节点
if(Father_node==null){
this.Father_node=this;
}
if(Goal_node==null){
this.Goal_node=this;
}
if(Start_node==null){
this.Start_node=this;
}
//如果没有,则等于自己(用于起点和重点)
if(Start_node!
=null){
Father_int=Start_node.getG();
}
//获得父类节点的代价
}
publicfunctiongetH():
int{
returnMath.abs(h-Goal_node.h)*10+Math.abs(l-Goal_node.l)*10;
//返回目标到当前的直线代价
}
publicfunctiongetG():
int{
if(Math.abs(h-Start_node.h)==1&&Math.abs(l-Start_node.l)==1){
return14+Father_int;
//返回父节点的带价值和当前节点到开始节点值等于一路走过来的带价值
}else{
returnMath.abs(h-Start_node.h)*10+Math.abs(l-Start_node.l)*10+Father_int;
//同上
}
}
//返回从开始节点到当前节点的移动代价
publicfunctiongetF():
int{
returngetH()+getG();
}
publicfunctionequals(me:
Object):
Boolean{
if(meisnode){
if(me.h==h&&me.l==l){
returntrue;
}else{
returnfalse;
}
}else{
returnfalse;
}
}
publicfunctiongetPath():
Array{
if(Father_node==this){
varccc:
Array=newArray();
ccc=[[h,l]];
returnccc;
}else{
varaa:
Array=Father_node.getPath();
varanswer:
Array=aa.slice(0,aa.length);
answer[aa.length]=[h,l];
returnanswer;
}
}
}
}
好了,节点我们搞定了,剩下的就是探路器的问题了(Find),那么我们的探路器的工作主要是负责,摆放节点到各个节点,那么这里我们需要知道什么位置需要放什么位置不需要,比如放过节点的位置当然就不需要了,呵呵,然后探路器会分析哪个节点返回的代价最少,最后当探路器找到目标之后会把整个路径返回出去,那么说到这里了,我们不得不说下列表问题,探路器是通过列表来选择是否放置节点,一共有2个,开启列表和关闭列表,首先,我们得知道,被放置的节点是否在某个列表里,如下函数:
publicfunctiondetection_listing(h:
int,l:
int,vec:
Array):
node{
for(vari:
int=0;ivarmc:
node=vec[i];
if(h==mc.h&&l==mc.l){
returnmc;
}
}
returnnull;
}
//检测是否在列表里(检测当前行当前列是否与开启列表里的元素一样)
现在我们只要把列表(数组)丢进去再丢行和列就好了,再下来,我们需要知道斜角的地方是否有障碍物,如下函数:
publicfunctionbianyuan(h:
int,l:
int,hh:
int,ll:
int,map:
Array):
Boolean{
if(hh-1==h&&ll-1==l){
if(map[h][l+1]==1||map[h+1][l]==1){
//trace("左上")
returnfalse;
}
}elseif(hh-1==h&&ll+1==l){
if(map[h][l-1]==1||map[h+1][l]==1){
//trace("右上")
returnfalse;
}
}elseif(hh+1==h&&ll-1==l){
if(map[h-1][l]==1||map[h][l+1]==1){
//trace("左下")
returnfalse;
}
}elseif(hh+1==h&&ll+1==l){
if(map[h][l-1]==1||map[h-1][l]==1){
//trace("右下")
returnfalse;
}
}
returntrue
//判断当前节点的斜角上周围是否有障碍物
}
我们需要丢一个行和列进去,然后再丢另一组行和列进去,然后是地图,判断第一组的行和列在第二组的行和列的什么位置,最后再判断是否有障碍物.if(map[h][l-1]==1||map[h-1][l]==1),这里我设置障碍物是1,再后来,我们需要知道是否找不到路:
if(Start_listing.length==0&&Current_node.equals(Goal_node)==false){
trace("没有路了")
kaishi=false
}
嗯,开启列表已经没有东西了,但是路径返回返回假,就说明寻路失败,我这里设置了一个布尔值为假:
好了,辅助函数都写好了,关键时刻到了,现在我们需要使用探路,探测身边周围的8个点,方法如下:
for(varj:
int=-1;j<2;j++){
for(vark:
int=-1;k<2;k++){
}
}
对,2个for循环,看看,当前节点的行加上J,和当前节点的列加上K,是否就是当前节点周围的8个点,好了,下面是上面的函数的完整应用:
publicfunctionaido(map:
Array){
if(shanchu.length!
=0){
for(vart:
int=0;tshanchu[t]=null;
trace("删除了")
}
}
//如果删除列表不为空,则删除里面所有元素,等待系统回收
shanchu=newArray();
//初始化删除列表
Start_listing=newArray;
//开启列表
Stop_listing=newArray;
//关闭列表
Start_listing.push(Start_node);
//把当前节点放入开启列表
shanchu.push(Start_node);
//把当前节点放入删除列表
do{
Current_node=Start_listing[0];
//获取开启列表的第一个元素
for(vari:
int=1;iif(Start_listing[i].getF()Current_node=Start_listing[i];
}
}
//比较数组里的元素,如果数组里的元素的F值比当前的小,则把当前的替换成数组里的
if(Current_node.equals(Goal_node)){
//trace("找到路了");
fruit=Current_node.getPath();
kaishi=true
break
}
//是否找到路径
Stop_listing.push(Current_node);
//添加当前节点到关闭列表
Start_listing.splice(Start_listing.indexOf(Current_node),1);
//删除开始列表把当前节点从
for(varj:
int=-1;j<2;j++){
for(vark:
int=-1;k<2;k++){
//遍历当前节点周围的位置
if(Current_node.h+j==0&&Current_node.l+k==0){
continue;
}
if(Current_node.h+j>=map.length||Current_node.h-j<0){
continue;
}
if(Current_node.l+k>=map[0].length||Current_node.l-k<0){
continue;
}
//判断是否遍历到了自己,是否超出了边界,如果是,则返回出去,重新遍历
if(bianyuan(Current_node.h+j,Current_node.l+k,Current_node.h,Current_node.l,map)&&detection_fraise(Current_node.h+j,Current_node.l+k,map)&&detection_border(Current_node.h+j,Current_node.l+k,map)&&detection_listing(Current_node.h+j,Current_node.l+k,Stop_listing)==null){
//如果周边的节点不在关闭列表里,是不是障碍物,斜角的周边是否有障碍物
About_node=detection_listing(Current_node.h+j,Current_node.l+k,Start_listing);
//判断周边的节点,传递行,列,开启列表
//如果当前对象在开启列表里,则函数会返回空,如果不在,则会创建新的节点
if(About_node==null){
//如果为空则不用多说了,像病毒一样扩散开来
About_node=newnode(Current_node.h+j,Current_node.l+k,Current_node,Goal_node,Current_node);
//初始化节点,传递行,列,父节点(当前的节点),目标节点(上面当鼠标点击时6了一个),开始节点(游戏运行时6了一个)
Start_listing.push(About_node);
shanchu.push(About_node);
//如果周边的节点为空,则在周边扩散节点,将扩散的节点放入开启列表,以备下次检测需要,也放入删除列表,待找到路径后删除删除列表里所有的节点,等待系统回收
}else{
varoldG:
int=About_node.getG();
//保存零时变量为开启列表里的G值
varFather:
node=About_node.Father_node;
//保存开启列表里的父节点
About_node.Father_node=Current_node;
//替换开启列表里的父节点为当前节点
if(About_node.getG()>=oldG){
//trace("被替换了")
//比较被替换父节点的开启列表里的节点的值是否大于最开始的G值
About_node.Father_node=Father;
//如果大于则,被替换的开启列表里的节点的父节点又被送了回来给与了开启列表里的节点
}
//如果周边节点移动的代价比当前节点移动的代价要少的话,就替换当前节点的父节点,递归路径时遍会递归到最短路径的父类
}
}
}
}
}while(Start_listing.length!
=0);
//如果开启列表为空,停止寻路
if(Start_listing.length==0&&Current_node.equals(Goal_node)==false){
trace("没有路了")
kaishi=false
}
}
首先,我们定义了一个寻路函数,只接收地图,然后我们初始化了开启列表和关闭列表,最后将当前节点放入开启列表和关闭列表,其实我们就是把起点放进去,仔细看,这个动作是在for循环外面,既然是寻路,所以我们要有个开头,对吧,最后我们用do...while()
循环来执行核心部分,在开始部分,我们先来了一次冒泡排序,就是找出开启列表里代价最小的节点,然后让零时节点等于这个节点,然后再比较,这个节点是否是目标节点,如果是,则找到路径,返回路径,如果不是,继续,只要是检测过的节点,都不需要再检测,所以我们把当前节点放入关闭列表,同时也在开启列表里删除当前节点,最后,我们通过for循环,检测当前节点周围的8个点,判断如果超出了边界则放弃掉,进行下一次循环,然后再判断当前节点是否不在关闭列表里,旁边是否有障碍物,自己本上是否是障碍物,如果都没有,则检测是否在开启列表里,如果也不在开启列表里,则放置节点到当前的点上,把当前的点放入开启列表,等待下一次的循环检测,如果在开启列表里,则检测开启列表里的G指是否小于当前的G值,如果小于,则替换父节点,因为递归寻路是通过父节点的坐标,我们需要保证我们找到的路径是最短的,如果开启列表不为空,则继续循环,好,至此整个探路器核心介绍完毕,下面是完整代码:
package{
publicclassFind{
privatevarh:
int;
privatevarl:
int;
privatevarfruit:
Array;
//路径数组
privatevarStart_listing:
Array;
//开启列表
privatevarStop_listing:
Array;
//关闭列表
publicvarGoal_node:
node;
//目标节点
publicvarStart_node:
node;
//开始节点
publicvarCurrent_node:
node;
//当前节点
publicvarFairly_node:
node;
//比较节点
publicvarAbout_node:
node;
//零时节点
privatevarshanchu:
Array;
staticpublicvarkaishi:
Boolean
publicfunctionAIdo(h:
int,l:
int,hh:
int,ll:
int,map:
Array):
Array{
shanchu=newArray();
//初始化删除列表
Start_listing=newArray;
//开启列表
Stop_listing=newArray;
//关闭列表
Goal_node=newnode(hh,ll,null,null,null);
//目标节点
Start_node=newnode(h,l,null,Goal_node,null);
//开始节点
aido(map);
//开始寻路
returnfruit;
//返回寻路后的结果数组
}
publicfunctionaido(map:
Array){
if(shanchu.length!
=0){
for(vart:
int=0;tshanchu[t]=null;
trace("删除了")
}
}
//如果删除列表不为空,则删除里面所有元素,等待系统回收
shanchu=newArray();
//初始化删除列表
Start_listing=newArray;
//开启列表
Stop_listing=newArray;
//关闭列表
Start_listing.push(Start_node);
//把当前节点放入开启列表
shanchu.push(Start_node);
//把当前节点放入删除列表
do{
Current_node=Start_listing[0];
//获取开启列表的第一个元素
for(vari:
int=1;iif(Start_listing[i].getF()Curr