工作中需要优化A*算法,研究了一天,最后取得了不错的效果。看网上的朋友还没有相关的研究,特此记录一下。有错误欢迎大家批评指正。如需转载请注明出处,http://www.cnblogs.com/Leonhard-/p/6842052.html,这是对作者最起码的尊重,谢谢大家。
本文结构如下:
一、A*算法优化背景介绍
二、A*算法介绍与实现简述
三、深入思考优化需求
1.启发函数的设计思路
2.启发函数与cost值的相对关系
3.启发函数中对k值大小的深入思考
四、总结
一、A*算法优化背景介绍
A*算法运用的场景很广泛,不同的运用场景有不同的A*设计思路,本文不是描述所有环境下的设计思路,而仅是记录工作中碰到的A*算法优化思路。使用的背景是在一个二维地图上,角色允许从周围的八个方向进行移动(如下图,角色在粉红色位置,周围蓝色位置为可走路线),地图中带有障碍物,求从某一个点到另外一个点的静态路径。具体的要求:1.让这个路径尽可能的短;2.让移动更加平滑,尽可能少的出现角色在选择方向上的抖动(后文有进一步介绍)。
二、A*算法介绍与实现简述
1.A*算法简单介绍
网上已经有朋友发了短小精干却内容清晰的A*算法教程,没有研究过A*算法的朋友可以先到这个链接学习基本A*算法(如果想进一步了解A*算法的种种相关信息,可以查看原版链接或者翻译版链接)。此处则不再对重复的信息进行复制粘贴。
2.工作当中的A*算法实现思路。
大概思路是这样的,代码用的是一个heap来存储open队列节点,用一个哈希表来存储close队列(当然也可以使用其他的方法,上述给的链接里有讲,本文就不再赘述)。
三、深入思考优化需求
1.估值函数的设计思路
其中估值函数如下:
int estimateValue(Point gaol,Point current)
{
int dx = abs(goal.x-current.x);
int dy = abs(goal.y-current.y);
if(dx > dy)
return *dx+*dy;
else
return *dy+*dx;
}
以dx>dy的情况为例,其中的10*dx+4*dy是什么意思呢?如下图,粉红色为需要考虑估值的点,棕色为终点,其中棕色线条的长度*10则为估值函数的估计值(放大10倍是为了去掉小数点),即10*(dx+0.4*dy)。
2.估值函数与cost值的相对关系
为了说明角色往不同方向移动的cost值,我们给图的格子标上数字,方便说明,如下图。公司代码中设定,往2,4,5,7方向移动,cost值为1,往1,3,6,8位置移动,则cost值为2。细心的朋友会发现,这个地方有了错误。上述我们为了将1位小数的浮点数转换为整数,把estimate值乘了个系数10,此处也应该乘以10。即往2,4,5,7方向移动,cost值为10,往1,3,6,8位置移动,则cost值为20。根据以往的习惯,1.当目的地在位置3时,我们鼓励玩家直接从粉红点跳到3;2.当目的地点在位置5右边一个格子时,我们鼓励玩家先到5,然后再移动到5右边的格子。如果往斜方向上移动的取值为20的话,我们发现会出现一个问题,就是从粉红点直接到3的cost等于先到5再到3的cost值,这样会引起移动时角色的“抖动”(角色经常在面向3和面向5的两个方向中切换,看起来就像在抖动);如果我们把往斜方向上移动的取值为10的话,这样斜方向上和水平方向上又有了新问题,因为从粉红色格子到3再到5右边的格子的cost值等于粉红色格子先到5再到5右边格子的cost值,这样看起来比较奇怪,当地图比较大时,会更明显,当角色要从地图左下角到右下角时,角色先到地图中间的最上方,然后又往右下角进行前进,而且会增加角色的“抖动”。那么往斜方向上移动的cost值的取值范围最好是属于(10,20),这里假设我们取14,后面介绍这会出现的问题。
为什么会出现角色抖动呢,分析以上我们可以知道,当estimate+cost值相等的时候,维护open队列的heap只能随机取一个,所以不能保证取出来的就一定是按我们想要的某一个方向前进的节点。那解决思路是什么呢,就是estimate+cost值尽量少的出现相等的情况,要么就是修改heap读取的方式,一目了然,前者才是个好方法。而上面的cost取值为14,经过计算我们发现,往右上方移动与往右边移动的estimate+cost的值相等,也会出现“抖动”。而此时我们将往斜上方移动的cost值取(10,14)或者(14,20),问题圆满解决了。那么往斜上方移动的cost值取(10,14)或者(14,20)有什么区别呢?看图来说明。
当我们取(10,14)时,寻路结果如下:
当我们取(14,20)的话,寻路结果如下:
结合上面的两幅图,我们清楚了cost值和dy系数(后文称为k)之间的关系(只考虑dx>dy的情况,dy>dx情况类似)。当斜方向与水平方向的cost的插值大于k时,寻路会优先往x方向走,一直走到dx == dy时候开始走斜线,反之则先走斜线,后走水平线。
3.估值函数中的k值的深入思考
上一小节我们讨论了估值函数中的k值和往斜上方走的cost值的相对关系,那么我们进一步思考,这个k值是否可以随便取呢。答案:是,但也不全是。为什么这么说呢?具体的k值对性能和速度有很大影响,如果k的选取让estimate<=从该点到目标节点的实际cost值,那么它能取到最优解,但是也因为estimate+cost的值较小的原因,heap考虑的节点数变多,让算法的速度减慢;反之,如果k的选取让estimate>从该点到目标节点的实际cost值,那么它不一定取到最优解,此时A*算法便不再是A*算法,沦为了Greedy first-search algorithm,因为estimate+cost的值较大的原因,heap考虑的节点数变少,让算法的速度加快,但是也不能保证最优解了。所以,为了取到最优解,k的值不能大于4。
四、总结
本文介绍了二维地图上A*算法的估值函数的设计思路并讨论了它与cost值的相对关系及其影响。综上所述,二维地图上为了保证让角色走最短路径,且让角色尽量不出现“抖动”,那么估值函数设计为 G = 10*dx + 4*dy就比较好。而且考虑到项目组的游戏地图为矩形如下图(黄色),建筑物的俯视图如下图(黑色),先往水平方向走比先往斜方向走好看,所以往斜上方走的cost值取(14,20)比较好。