A算法.docx
《A算法.docx》由会员分享,可在线阅读,更多相关《A算法.docx(13页珍藏版)》请在冰豆网上搜索。
A算法
A*算法
A*算法思想(启发函数法)
A*在游戏设计中有它很典型的用法,是人工智能在游戏中的代表。
A*算法属于一种启发式搜索。
它扩展结点的次序类似于广度优先搜索,但不同的是每生成一个子结点需要计算估价函数F,以估算起始结点到该结点的代价及它到达目标结点的代价的和;每当扩展结点时,总是在所有待扩展结点中选择具有最小F值的结点作为扩展对象,以便使搜索尽量沿最有希望的方向进行。
因此,A*算法只要求产生问题的全部状态空间的部分结点,就可以求解问题了,搜索效率较高。
确定估价函数方法通常是:
搜索到该结点的深度+距离目标最近的程度。
一、何谓启发式搜索算法:
在说它之前先提提状态空间搜索。
状态空间搜索,如果按专业点的说法就是将问题求解过程表现为从初始状态到目标状态寻找这个路径的过程。
通俗点说,就是在解一个问题时,找到一条解题的过程可以从求解的开始到问题的结果(好象并不通俗哦)。
由于求解问题的过程中分枝有很多,主要是求解过程中求解条件的不确定性,不完备性造成的,使得求解的路径很多这就构成了一个图,我们说这个图就是状态空间。
问题的求解实际上就是在这个图中找到一条路径可以从开始到结果。
这个寻找的过程就是状态空间搜索。
常用的状态空间搜索有深度优先和广度优先。
广度优先是从初始状态一层一层向下找,直到找到目标为止。
深度优先是按照一定的顺序前查找完一个分支,再查找另一个分支,以至找到目标为止。
这两种算法在数据结构书中都有描述,可以参看这些书得到更详细的解释。
前面说的广度和深度优先搜索有一个很大的缺陷就是他们都是在一个给定的状态空间中穷举。
这在状态空间不大的情况下是很合适的算法,可是当状态空间十分大,且不预测的情况下就不可取了。
他的效率实在太低,甚至不可完成。
在这里就要用到启发式搜索了。
启发式搜索就是在状态空间中的搜索对每一个搜索的位置进行评估,得到最好的位置,再从这个位置进行搜索直到目标。
这样可以省略大量无畏的搜索路径,提到了效率。
在启发式搜索中,对位置的估价是十分重要的。
采用了不同的估价可以有不同的效果。
我们先看看估价是如何表示的。
启发中的估价是用估价函数表示的,如:
f(n)=g(n)+h(n)
其中f(n)是节点n的估价函数,g(n)实在状态空间中从初始节点到n节点的实际代价,h(n)是从n到目标节点最佳路径的估计代价。
在这里主要是h(n)体现了搜索的启发信息,因为g(n)是已知的。
如果说详细点,g(n)代表了搜索的广度的优先趋势。
但是当h(n)>>g(n)时,可以省略g(n),而提高效率。
二、初识A*算法:
启发式搜索其实有很多的算法,比如:
局部择优搜索法、最好优先搜索法等等。
当然A*也是。
这些算法都使用了启发函数,但在具体的选取最佳搜索节点时的策略不同。
象局部择优搜索法,就是在搜索的过程中选取“最佳节点”后舍弃其他的兄弟节点,父亲节点,而一直得搜索下去。
这种搜索的结果很明显,由于舍弃了其他的节点,可能也把
最好的节点都舍弃了,因为求解的最佳节点只是在该阶段的最佳并不一定是全局的最佳。
最好优先就聪明多了,他在搜索时,便没有舍弃节点(除非该节点是死节点),在每一步的估价中都把当前的节点和以前的节点的估价值比较得到一个“最佳的节点”。
这样可以有效的防止“最佳节点”的丢失。
那么A*算法又是一种什么样的算法呢?
其实A*算法也是一种最好优先的算法。
只不过要加上一些约束条件罢了。
由于在一些问题求解时,我们希望能够求解出状态空间搜索的最短路径,也就是用最快的方法求解问题,A*就是干这种事情的!
我们先下个定义,如果一个估价函数可以找出最短的路径,我们称之
为可采纳性。
A*算法是一个可采纳的最好优先算法。
A*算法的估价函数可表示为:
f'(n)=g'(n)+h'(n)
这里,f'(n)是估价函数,g'(n)是起点到终点的最短路径值,h'(n)是n到目标的最断路经的启发值。
由于这个f'(n)其实是无法预先知道的,所以我们用前面的估价函数f(n)做近似。
g(n)代替g'(n),但g(n)>=g'(n)才可(大多数情况下都是满足的,可以不用考虑),h(n)代替h'(n),但h(n)<=h'(n)才可(这一点特别的重要)。
可以证明应用这样的估价函数是可以找到最短路径的,也就是可采纳的。
A*算法的程序编写原理
在《初识A*算法》中说过,A*算法是最好优先算法的一种。
只是有一些约束条件而已。
我们先来看看最好优先算法是如何编写的吧。
如图有如下的状态空间:
(起始位置是A,目标位置是P,字母后的数字表示节点的估价值)
A - 5
/ │ \
/ │ \
↓ ↓ \
B-4 C-4 D-6
/| /\ /\
/ | / \ / \
/ | | ↓ | \
E-5 F-5 G-4 H-3 I J
/\ /\| /\ /\ \
/ \/ || / \ / \ \
/ v || ↓ ↓/ \ \
K L MN O-2 P-3 QR
| |
||
||
ST
搜索过程中设置两个表:
OPEN和CLOSED。
OPEN表保存了所有已生
成而未考察的节点,CLOSED表中记录已访问过的节点。
算法中有一步
是根据估价函数重排OPEN表。
这样循环中的每一步只考虑OPEN表中状
态最好的节点。
具体搜索过程如下:
1)初始状态:
OPEN=[A5];CLOSED=[];
2)估算A5,取得搜有子节点,并放入OPEN表中;
OPEN=[B4,C4,D6];CLOSED=[A5]
3)估算B4,取得搜有子节点,并放入OPEN表中;
OPEN=[C4,E5,F5,D6];CLOSED=[B4,A5]
4)估算C4;取得搜有子节点,并放入OPEN表中;
OPEN=[H3,G4,E5,F5,D6];CLOSED=[C4,B4,A5]
5)估算H3,取得搜有子节点,并放入OPEN表中;
OPEN=[O2,P3,G4,E5,F5,D6];CLOSED=H3C4,B4,A5]
6)估算O2,取得搜有子节点,并放入OPEN表中;
OPEN=[P3,G4,E5,F5,D6];CLOSED=[O2,H3,C4,B4,A5]
7)估算P3,已得到解;
看了具体的过程,再看看伪程序吧。
算法的伪程序如下:
Best_First_Search()
{
Open=[起始节点];Closed=[];
while(Open表非空)
{
从Open中取得一个节点X,并从OPEN表中删除。
if(X是目标节点)
{
求得路径PATH;返回路径PATH;
}
for(每一个X的子节点Y)
{
if(Y不在OPEN表和CLOSE表中)
{
求Y的估价值;并将Y插入OPEN表中;//还没有排序
}
else
if(Y在OPEN表中)
{
if(Y的估价值小于OPEN表的估价值)
更新OPEN表中的估价值;
}
else//Y在CLOSE表中
{
if(Y的估价值小于CLOSE表的估价值)
{
更新CLOSE表中的估价值;
从CLOSE表中移出节点,并放入OPEN表中;
}
}
将X节点插入CLOSE表中;
按照估价值将OPEN表中的节点排序;
}//endfor
}//endwhile
}//endfunc
啊!
伪程序出来了,写一个源程序应该不是问题了,依葫芦画瓢就可以。
A*算法的程序与此是一样的,只要注意估价函数中的g(n)的h(n)约束
条件就可以了。
不清楚的可以看看《初识A*算法》。
好了,我们可以
进入另一个重要的话题,用A*算法实现最短路径的搜索。
在此之前你
最好认真的理解前面的算法。
三、用A*算法实现最短路径的搜索
在游戏设计中,经常要涉及到最短路径的搜索,现在一个比较好的方法
就是用A*算法进行设计。
他的好处我们就不用管了,反正就是好!
^_*
注意下面所说的都是以ClassAstar这个程序为蓝本,你可以在这里
下载这个程序。
这个程序是一个完整的工程。
里面带了一个EXE文件。
可以先看看。
先复习一下,A*算法的核心是估价函数f(n),它包括g(n)和h(n)两部分.
g(n)是已经走过的代价,h(n)是n到目标的估计代价。
在这个例子
中g(n)表示在状态空间从起始节点到n节点的深度,h(n)表示n节点
所在地图的位置到目标位置的直线距离。
啊!
一个是状态空间,一个
是实际的地图,不要搞错了。
再详细点说,有一个物体A,在地图上
的坐标是(xa,ya),A所要到达的目标b的坐标是(xb,yb)。
则开始搜索
时,设置一个起始节点1,生成八个子节点2-9因为有八个方向。
如图:
节点1g
(1)=0
/||\h
(1)=(Xb*Xb-Xa*Xa)+(Yb*Yb-Ya*Ya)
//|\
//|\
节点2节点3节点4...节点9g(9)=1
/|\h(9)=(Xb*Xb-X9*X9)+(Yb*Yb-Y9*Y9)
/|\
/ | \
节点10节点11...节点17g(17)=2
h(17)=(Xb*Xb-X17*X17)+(Yb*Yb-Y17*Y17)
仔细看看节点1、9、17的g(n)和h(n)是怎么计算的。
现在应该知道了
下面程序中的f(n)是如何计算的吧。
开始讲解源程序了。
其实这个
序是一个很典型的教科书似的程序,也就是说只要你看懂了上面的
伪程序,这个程序是十分容易理解的。
不过他和上面的伪程序有一些
的不同,我在后面会提出来。
先看搜索主函数:
voidAstarPathfinder:
:
FindPath(intsx,intsy,intdx,intdy)
{
NODE*Node,*BestNode;
intTileNumDest;
//得到目标位置,作判断用
TileNumDest=TileNum(sx,sy);
//生成Open和Closed表
OPEN=(NODE*)calloc(1,sizeof(NODE));
CLOSED=(NODE*)calloc(1,sizeof(NODE));
//生成起始节点,并放入Open表中
Node=(NODE*)calloc(1,sizeof(NODE));
Node->g=0;
//这是计算h值
Node->h=(dx-sx)*(dx-sx)+(dy-sy)*(dy-sy);//此处按道理应用开方
//这是计算f值,即估价值
Node->f=Node->g+Node->h;
Node->NodeNum=TileNum(dx,dy);
Node->x=dx;
Node->y=dy;
OPEN->NextNode=Node;//makeOpenListpointtofirstnode
for(;;)
{//从Open表中取得一个估价值最好的节点
BestNode=ReturnBestNode();
//如果该节点是目标节点就退出
if(BestNode->NodeNum==TileNumDest)//ifwe'vefoundthe
//end,breakandfinish
break;
//否则生成子节点
GenerateSuccessors(BestNode,sx,sy);
}
PATH=BestNode;
}
再看看生成子节点函数GenerateSuccessors:
voidAstarPathfinder:
:
GenerateSuccessors(NODE*BestNode,intdx,intdy)
{
intx,y;
//哦!
依次生成八个方向的子节点,简单!
//Upper-Left
if(FreeTile(x=BestNode->x-TILESIZE,y=BestNode->y-TILESIZE))
GenerateSucc(BestNode,x,y,dx,dy);
//Upper
if(FreeTile(x=BestNode->x,y=BestNode->y-TILESIZE))
GenerateSucc(BestNode,x,y,dx,dy);
//Upper-Right
if(FreeTile(x=BestNode->x+TILESIZE,y=BestNode->y-TILESIZE))
GenerateSucc(BestNode,x,y,dx,dy);
//Right
if(FreeTile(x=BestNode->x+TILESIZE,y=BestNode->y))
GenerateSucc(BestNode,x,y,dx,dy);
//Lower-Right
if(FreeTile(x=BestNode->x+TILESIZE,y=BestNode->y+TILESIZE))
GenerateSucc(BestNode,x,y,dx,dy);
//Lower
if(FreeTile(x=BestNode->x,y=BestNode->y+TILESIZE))
GenerateSucc(BestNode,x,y,dx,dy);
//Lower-Left
if(FreeTile(x=BestNode->x-TILESIZE,y=BestNode->y+TILESIZE))
GenerateSucc(BestNode,x,y,dx,dy);
//Left
if(FreeTile(x=BestNode->x-TILESIZE,y=BestNode->y))
GenerateSucc(BestNode,x,y,dx,dy);
}
看看最重要的函数GenerateSucc:
voidAstarPathfinder:
:
GenerateSucc(NODE*BestNode,intx,inty,intdx,intdy)
{
intg,TileNumS,c=0;
NODE*Old,*Successor;
//计算子节点的g值
g=BestNode->g+1;//g(Successor)=g(BestNode)+costofgetting
//fromBestNodetoSuccessor
TileNumS=TileNum(x,y);//identificationpurposes
//子节点再Open表中吗?
if((Old=CheckOPEN(TileNumS))!
=NULL)//ifequaltoNULLthen
//notinOPENlist,elseitreturnstheNodeinOld
{
//若在
for(c=0;c<8;c++)
if(BestNode->Child[c]==NULL)//AddOldtothelistof
//BestNode'sChildren(orSuccessors).
break;
BestNode->Child[c]=Old;
//比较Open表中的估价值和当前的估价值(只要比较g值就可以了)
if(gg)//ifournewgvalueis//resetOld'sparenttopointtoBestNode
{
//当前的估价值小就更新Open表中的估价值
Old->Parent=BestNode;
Old->g=g;
Old->f=g+Old->h;
}
}
else//在Closed表中吗?
if((Old=CheckCLOSED(TileNumS))!
=NULL)//ifequaltoNULLthen
//notinOPENlist,elseitreturnstheNodeinOld
{
//若在
for(c=0;c<8;c++)
if(BestNode->Child[c]==NULL)//AddOldtothelistof
//BestNode'sChildren(orSuccessors).
break;
BestNode->Child[c]=Old;
//比较Closed表中的估价值和当前的估价值(只要比较g值就可以了)
if(gg)//ifournewgvalueis//resetOld'sparenttopointtoBestNode
{
//当前的估价值小就更新Closed表中的估价值
Old->Parent=BestNode;
Old->g=g;
Old->f=g+Old->h;
//再依次更新Old的所有子节点的估价值
PropagateDown(Old);//Sincewechangedthegvalueof
//Old,weneedtopropagatethisnew
//valuedownwards,i.e.
//doaDepth-Firsttraversalofthetree!
}
}
else//不在Open表中也不在Close表中
{
//生成新的节点
Successor=(NODE*)calloc(1,sizeof(NODE));
Successor->Parent=BestNode;
Successor->g=g;
Successor->h=(x-dx)*(x-dx)+(y-dy)*(y-dy);//shoulddo
//sqrt(),butsincewedon'treally
Successor->f=g+Successor->h;//careaboutthedistancebut
//justwhichbranchlooks
Successor->x=x;//betterthisshouldsuffice.
//Anyayzit'sfaster.
Successor->y=y;
Successor->NodeNum=TileNumS;
//再插入Open表中,同时排序。
Insert(Successor);//InsertSuccessoronOPENlistwrtf
for(c=0;c<8;c++)
if(BestNode->Child[c]==NULL)//AddOldtothe
//listofBestNode'sChildren(orSuccessors).
break;
BestNode->Child[c]=Successor;
}
}
A*算法例题与习题
例1:
8数码难题:
283123
164->84(用最少的步数)
75765
用A*算法程序如下:
programnum8;
typea33=array[1..3,1..3]of0..8;
a4=array[1..4]of-1..1;
node=record
ch:
a33;
si,sj:
1..3;
f:
byte;
pnt,dep,next:
byte;
end;
constgoal:
a33=((1,2,3),(8,0,4),(7,6,5));
start:
a33=((2,8,3),(1,6,4),(7,0,5));
di:
a4=(0,-1,0,1);
dj:
a4=(-1,0,1,0);
vardata:
array[0..100]ofnode;
temp:
node;
r,k,ni,nj,head,tail,depth:
integer;
functioncheck(k:
integer):
boolean;
begin
ni:
=temp.si+di[k];nj:
=temp.sj+dj[k];
if(niin[1..3])and(njin[1..3])thencheck:
=trueelsecheck:
=false;
end;
functiondupe:
boolean;
vari,j,k:
integer;
buf:
boolean;
begin
buf:
=false;i:
=0;
repeat
inc(i);buf:
=true;
forj:
=1to3do
fork:
=1to3do
ifdata[i].ch[j,k]<>data[tail].ch[j,k]thenbuf:
=false;
untilbufor(i>=tail-1);
dupe:
=buf;
end;
functiongoals:
boolean;
vari,j:
byte;
begin
goals:
=true;
fori:
=1to3do