A-Star(A*)算法

来源:互联网 发布:西安广电网络费用 编辑:程序博客网 时间:2024/05/17 15:38

A-star 算法


本章由cowboylym(酒劍仙采葡萄)编写,转载请注明出处。 

作者:酒劍仙采葡萄    邮箱: cowboyleo@live.com   

原文地址http://www.gamedev.net/page/resources/_/technical/artificial-intelligence/a-pathfinding-for-beginners-r2003

限于自己稀烂的英语水平(6级都没过- -) ,大家就凑活着看吧,如有原则性的错误欢迎大家及时指正

1、入门:搜索区域

我们假设有人想从A点移动到B点。有一堵墙将A、B两点隔开。见下图所示。绿色表示点A,红色表示点B。被蓝色填充的矩形区域,表示隔在中间的墙。

图1

首先需要注意的是,我们已将整个搜索区域划分成一个正方形网格。这叫简化搜索区域,作为路径查找的第一步。这种特别的方法将我们的搜索区域归纳为一个二维数组。数组中的每个元素表示网格中的一个方格,它的状态被记为walkable或unwalkable。我们通过挑选出合适的方格来找到从A到B的路径。当路径一旦找到,我们将从一个方格的中心移动到下一块方格的中心,直到抵达目的地。

 

这些中心点叫做“节点”。我们在别处阅读有关路径搜索的资料时,经常看到人们讨论“节点”。为什么不直接叫他们方格呢?因为我们可以将搜索区域划分成方格以外的其他形状。可以是长方形,六角形,三角形,或者其他形状。节点可以放置在这些图形内的任意位置——在图形中心或者沿着图形的边缘,或者其他任何位置。然而,我们使用这个系统(方格系统),是因为它是最简单的!

 

2、开始搜索

一旦我们将搜索区域简化为一组易管理的节点,我们已经完成了上面网格的布局,下一步就是进行搜索,找到最短路径。我们从A点开始,检查相邻的方格,然后继续向外搜索,直到找到目标。

 

我们执行以下几点开始搜索:

1、           从起点A开始,先将它添加至一个“open list”方格列表。open list 就像是一个购物清单。现在这个清单上只有1项。但是接下来我们会用更多项。该列表所包含的方格,可能是沿着路径上的,也可能不是沿着路径上的。总的来说,该列表中的方格都是要被检测的。

2、           注意与A点相邻的,能够抵达的或walkable的所有方格(忽略 有墙有水或其他非法地形的方格),将他们也加入到open list。对于这些节点而言,A节点被视为它们的父节点。当我们跟踪路径的时候,父节点方格起到非常重要的作用。接下来会详细介绍。

3、           将方格A从open list中取出,将其放至closed list中,从现在开始,你就不需要再管它了。

 

这里,你可能需要如下的一些插图说明。图中深绿色的方格是你的起始方格。淡蓝色的轮廓表示这个方格已经被添加到closed list。现在open list中所有相邻的方格都被检查过了,用绿色的线将它们括起来。它们每个都有一个灰色的指针指回父节点(起始方格)

图2

接下来,根据下面的描述,我们从open list中选择一个相邻的方格,或多或少的重复前面的步骤。但是我们应该选择哪个方格呢?F值最小的那个

 

3、路径评分

在挑选最佳路径所需要的方格时,下面的方程是决定的关键。

F = G + H

在这里:

G = 沿着起点A到给定点所生成的路径上移动所花费的消耗。

H = 从给定点移动到终点B预计所花费的消耗。这个通常被称为启发式算法,可能令人有点难以理解。这是因为他是一种猜测。在我们找到路径之前,我们确实不知道实际距离。因为各种各样的东西(墙,水 等等)可能挡住道路。再教程中我们给出了一种计算H的方法,但在网上其他的文章中你可以找到许多别的方法。

 

我们通过反复检查open list并找出具有最小F值的方格的方法来生成路径。这个过程将在稍后详细介绍。首先让我先弄清楚如何计算这个方程。

 

如上所述,G是沿着起点到给定点所生成的路径上移动所花费的消耗。在本例中我们分配10点(消耗)在水平和垂直的方格中,分配14点在斜对角线的方格中。我们使用这些数字是因为斜对角线移动的实际距离是水平或垂直移动距离的根号2倍,也就是大概是1.414倍。我们使用10和14是做一个粗略的计算。这个比例大致是对的,它避免了平方根和小数计算。但这并不意味着我们愚蠢或者讨厌数学。使用这样的整数,同样使计算机的计算速度更快。很快我们就会发现如果我们不使用这样的捷径,路径查询会变得很缓慢。

 

因为我们正在沿着给定的方格所指定的路径计算G值,所以找出那块方格G值的方法是用该方格的父节点方格的G值加上10或者14(取决于是水平垂直的还是斜对角线上的)。在本示例的后面,当我们远离起始点获得更多的格子时,这种方法将显得非常重要。

 

H的估算方法有很多。这里我们使用曼哈顿方法。我们计算从当前方格到目标方格的水平和垂直移动的总和(忽略斜对角线移动和道路上的障碍物)。然后我们将总和乘以10(10表示我们水平或垂直移动一格的消耗)。之所以叫曼哈顿法可能是因为这个方法就像在计算从一个地区到另一个地区的街区数量,并且你不能斜着横跨街区。

 

读这些描述的时候,你可能猜想这个启发式算法仅仅是在艰苦的估算当前方格到目标点的(沿直线走)剩余距离。其实不仅仅是这样,我实际上是在估计沿着路径(通常更远)移动的剩余距离。距离越近这个估算就越接近真实值,距离越远这个算法就越快。如果我们高估了这个距离,它将不能保证给我们最短路径。在这种情况下我们称之为“不被许可的启发式算法”。

 

从技术上讲,在本例中,曼哈顿法是不被认可的,因为它稍微高估了剩余距离。但我们依然使用它,因为它使得我们意图变得更加容易理解,并且它只是稍稍高估了。在极少数偶然的情况下结果不是最短的路径时,也可以近似的认为是最短的。想了解更多么?点击这里你可以了解更多关于启发式算法的方程式和附加说明。

F是G与H的和。我们搜索的第一步的计算结果可以在下图中看到。F、G和H的评分被写在每个方格中,如起点方格右侧方格所示,F在左上角,G在左下角,H在右下角。

图3

现在让我们看看这些方格,在有字母的方格中,G=10。因为该方格在起始方格的水平方向上,在起始方格的上方,下方以及左方的方格中的G同样是10,对角线上的方格G为14。

H评分由曼哈顿启发式算法计算到红色目标的距离而得,只进行水平或垂直移动并且忽略道路上的墙。通过这种方法起点正右边的方格距离红色方格3个格子,所以H=30。该方格正上方的方格距离红色方格4个格子的距离所以H=40。由此你可以看出其他格子的H值都是如何算出来了吧。

每个格子的F评分都是由G与H相加而得。

4、继续搜索

继续搜索,我们仅仅从open list中的方格中选出F分最低的那个。然后对选中的方格我们做如下操作:

1、           将其从open list中取出放入closed list中。

2、           检测所有相邻的格子。忽略那些在closed list中已有的或者无法移动的格子(有墙,水或者其他非法地形)。如果这些格子还不在open list中,那将他们加入open list使选中的格子成为新格子的父节点。

3、             如果一个相邻的格子已经在open list中,查看存在更好的到达该格子的路径,换句话说,如果我们当前路径到达该格子所需的G值是否比之前的G值小。如果不是的话,那我们什么都不做。

另一方面,如果新路径的G值更小的话,将相邻方格的父节点变为选中的方格(在上图中,将指针的箭头方向改指向选中的方格),最后重新计算F和G的值。似乎听起来有点迷糊,下面我们将给出图表。

 

OK,让我们看看这是如何工作的。在最初的9个格子中,当我们将起点格子放进closed list以后。open list还乘8个格子,在起点格子正右方的格子拥有最低的F值40。所以我们选择该方格为我们的下一个格子,在下图用蓝色突出显示出来。

图4

首先,我们将选中的方格从open list中取出并放进closed list中(这就是为什么它现在要用蓝色突出显示出来)。然后我们检查相邻的方块。方块右侧是墙,所以我们忽略它们。左侧是起点方格。它在closed list中,所以我们也忽略它。

 

其余4个方格都已经在open list中,所以我们需要检查是否从选中方块去那些方块的路径会更好,我们以G值为参考值。让我们看看选中方块的正上方那个方块。它现在的G值是14。如果我们取而代之的从选中方格到达那个方格及从起点A先水平向右走一格10分再垂直向上走一格10分,那么此格的G值将变为10+10=20。20大于14,所以它不是一条更好的路径。从图中你可以看出这个是讲得通的。从起始点A直接走对角线,比先水平向右移动一格再垂直向上移动一格更快捷。当我们对open list中的4个相邻的格子重复以上操作后,我们发现走当前格子的路径没有一条是对我们有益的,所以我们不做任何改变。我们对当前格子做完所有操作后,我们看向所有相邻的格子,准备好移动到下一个格子。

 

所以当我们检查open list中的方格时,open list中的格子减少到7个,我们从中取出F值最小的,有趣的是在这里有两个格子的分数是54。那么我们选哪个呢?这并不重要,出于速度的考虑,我们选择两个格子中最后一个加进open list的那个,运算速度会快一点。当你已经接近目标的时候,这种方格搜索的偏差将会浮现出来。但这真的没关系。(不同处理方法导致不同版本的A*算法,算出长度相同的两条不同路径)。

那就让我们选择当前选中方格正下方的那个格子吧,如图所示该格子在起点格子的右边。

图5

此时,当我们检查相邻格子的时候,我们的正右方是一个带墙的格子,所以忽略它。右上方的的格子也同样带墙所以也忽略它。为什么我们也要忽略右下方的格子呢?因为我们从当前格子无法直接穿过墙角到达它。你需要先向下,再向右平移,方可到达。(注意:这种墙角的处理方法并不都是这样。这取决于你的节点是如何放置的。)

 

剩下的5个方格。当前方格正下方的2个方格还不在open list中,我们将他俩加到open list中,并将他们的父节点指针指向当前选中节点,对于另外3个,其中2个已经在closed list中了(左上方的起始点 和正上方的方格 他们都用蓝色突显出来了),所以我们忽略他们,最后一块在当前格子正左方的,检测它的G值沿着当前的路径走是否有所降低,答案是“否”。至此我们就完成了这一轮的检测工作了。

 

我们重复着这个过程,直到我们将目标方格放到closed list中。这个过程看上去就像下面的图例。

图6

注意起始点正下方第二格,它的父指针相较于先前的图,指向发生了变化。之前它的父指针指向右上方的的格子G值是28,现在它有另一个选择,走正上方的那个格子路径G值是20,这个常发生在我们搜索路径的某个地方,当G值在一条新的路径上计算出更小的值时,父指针的指向将改变,G和F的值也要重新计算。但在这个例子中这种改变是不重要的,因为有大量可能的解决方案对最佳路径的选择起了重要作用。

 

所以我们如何决定最佳路径呢?简便方法,就从红色的目的地方格开始,沿着它的父指针的箭头指向往回移动,最终移动到起点方格,得到的这条路径就是最佳路径,如下图所示。从起点A移动到终点B:就是从每个节点的中心沿着路径方向移动到下一个方格的中心,直至到达目标点的过程。


图7

5、A*方法的概要

OK,既然你已经看过了A*方法解释,那我们就将该方法在这里一步一步的梳理出来:

1、   将起点方格(或节点)加入open list。

2、   重复下列操作:

a)           取open list中F值最小的方格,并将它视为当前方格。

b)           将它放到closed list中。

c)           对于当前方格相邻的8个方格…

l  如果他的状态是”不可移动到的”或者它已经在closed list中,那么忽略它,否则做如下操作.

l  如果它不在open list中,将它放到open list中,并将它的父指针指向当前选中方格.记录F、G以及H的值。

l  如果它已经在open list中,使用G值来检测是否走当前这条路径对于该方格更有益。如果G值变低,说明这是一条更好的路径。如果是,那么将其父指针指向当前选中方格,并重新计算该方格的G、F值。如果你的open list是按F值排序的,那么你需要重新排序了。

d)          当你遇到如下情况时终止路径检测:

l  将目标方格加入closed list中,最佳路径被找出来了(见下面的注释),或者

l  无法找到目标方格,open list已经空了。在这种情况下,表示没有最佳路径。

3、   保存路径。从目的地方格开始,沿着它的父指针的箭头指向往回移动,最终移动到起点方格,这条路径就是你的最佳路径了。

 

 

注意:在该文早期的版本,建议你,当目标方格加入open list而非closed list中时就可以停止检测了。这可以使你更快的找到最短路径,但也并非总是如此。使用这种方式的解决方案在离终点倒数第2块方格与最后1个方格之间的移动将会发生显著的变化——例如在两点之间跨越了一条河。


至此,基本原理部分翻译完毕了,原文 还有一些原作者的题外话。这里就不赘述了,有兴趣的小伙伴可以阅读原文。


示例代码

0 0