A Star寻路算法简介

字数 1578阅读 2679

前言

如果你是一个游戏开发者,或者开发过一些关于人工智能的游戏,你一定知道A star算法,如果没有接触过此类的东东,那么看了这一篇文章,你会对A star算法从不知道变得了解,从了解变得理解。

A star算法原理

假设有人想从A点移动到一墙之隔的B点,如下图,绿色是起点A,红色是终点B,蓝色方块是中间的墙。
A star寻路算法

简化寻路问题
搜索区域被划分为方形网格,简化搜索区域,是寻路的第一步。这一方法把搜索区域简化成了一个二维数组。数组的每一个元素是网格的一个方块,方块被标记为可通过和不可通过。路径描述为从A点到B点所经过的方块的集合。

Open和Closed列表
在A star寻路算法中,我们通过从A开始,检查相邻方格的方式,向外扩展直至找到目标。首先需要两个列表:

  • 一个记录所有被考虑来寻找最短路径的网格集合(open list)
  • 一个记录下不会被考虑的网格集合(closed list)

首先在closed列表中添加当前位置A,然后把所有和它当前位置相邻的可通行网格添加到open列表中。如何选择哪个网格是最短路径呢?在A star算法中,通过给每个网格赋值权重,被称为路径增量。

路径增量
我们将会给每个网格一个G+H的权重值:
G是从开始点A到当前方块的移动量。所以从开始点A到相邻小方块的移动量为1,该值会随着离开始点越来越远而增大。

H是从当前方块到目标点B的移动量估算值。该值经常被称为启发式的,因为我们不确定移动量是多少 – 仅仅是一个估算值。

A star算法

A*寻路算法

算法分析
通过一个简单的例子来介绍算法:

正如上面所说,G表示沿路径从起点到当前点的移动耗费。在这个例子里,我们令水平或者垂直移动的耗费为10,对角线方向耗费为14。我们取这些值是因为沿对角线的距离是沿水平或垂直移动耗费的的根号2,或者约1.414倍。为了简化,我们用10和14近似。比例基本正确,同时我们避免了求根运算和小数。这不是只因为我们怕麻烦或者不喜欢数学。使用这样的整数对计算机来说也更快捷。你不久就会发现,如果你不使用这些简化方法,寻路会变得很慢。

既然我们在计算沿特定路径通往某个方格的G值,求值的方法就是取它父节点的G值,然后依照它相对父节点是对角线方向或者直角方向(非对角线),分别增加14和10。(某个方格的G值=父节点的G值+10或者+14)例子中这个方法的需求会变得更多,因为我们从起点方格以外获取了不止一个方格。

H值可以用不同的方法估算。我们这里使用的方法被称为曼哈顿方法,它计算从当前格到目的格之间水平和垂直的方格的数量总和,忽略对角线方向。然后把结果乘以10。这被成为曼哈顿方法是因为它看起来像计算城市中从一个地方到另外一个地方的街区数,在那里你不能沿对角线方向穿过街区。很重要的一点,我们忽略了一切障碍物。这是对剩余距离的一个估算,而非实际值,这也是这一方法被称为启发式的原因。

F的值是G和H的和。第一步搜索的结果可以在下面的图表中看到。F,G和H的评分被写在每个方格里。正如在紧挨起始格右侧的方格所表示的,F被打印在左上角,G在左下角,H则在右下角。


A*寻路算法

在最初的9格方格中,在起点被切换到关闭列表中后,还剩8格留在开启列表中。这里面,F值最低的那个是起始格右侧紧邻的格子,它的F值是40。因此我们选择这一格作为下一个要处理的方格。在紧随的图中,它被用蓝色突出显示。


A*寻路算法

首先,我们把它从open列表中取出,放入closed列表。然后检查相邻的格子,右侧的三个格子是墙,直接略过,左侧的一个格子是起始格(它在closed列表里),也跳过它。其他4格已经在open列表里了,于是我们检查G值来判定,如果通过这一格到达那里,路径是否更好。我们来看选中格子下面的方格。它的G值是14。如果我们从当前格移动到那里,G值就会等于20(到达当前格的G值是10,移动到上面的格子将使得G值增加10)。因为G值20大于14,所以这不是更好的路径。如果你看图,就能理解。与其通过先水平移动一格,再垂直移动一格,还不如直接沿对角线方向移动一格来得简单。当我们对已经存在于开启列表中的4个临近格重复这一过程的时候,我们发现没有一条路径可以通过使用当前格子得到改善,所以我们不做任何改变。既然我们已经检查过了所有邻近格,那么就可以移动到下一格了。于是我们检索open列表,现在里面只有7格了,我们仍然选择其中F值最低的。有趣的是,这次,有两个格子的数值都是54。我们如何选择?这并不麻烦。从速度上考虑,选择最后添加进列表的格子会更快捷。这种导致了寻路过程中,在靠近目标的时候,优先使用新找到的格子的偏好。但这无关紧要(对相同数值的不同对待,导致不同版本的A star算法找到等长的不同路径)。那我们就选择起始格右下方的格子,如图。
A*寻路算法

这次,当我们检查相邻格的时候,发现右侧是墙,于是略过。上面一格也被略过。我们也略过了墙下面的格子。为什么呢?因为你不能在不穿越墙角的情况下直接到达那个格子。你的确需要先往下走然后到达那一格,按部就班的走过那个拐角。(注解:穿越拐角的规则是可选的。它取决于你的节点是如何放置的。)这样一来,就剩下了其他5格。当前格下面的另外两个格子目前不在开启列表中,于是我们添加他们,并且把当前格指定为他们的父节点。其余3格,两个已经在关闭列表中(起始格,和当前格上方的格子,在表格中蓝色高亮显示),于是我们略过它们。最后一格,在当前格的左侧,将被检查通过这条路径,G值是否更低。不必担心,我们已经准备好检查开启列表中的下一格了。我们重复这个过程,直到目标格被添加进关闭列表(注解),就如在下面的图中所看到的。


A*寻路算法

注意,起始格下方格子的父节点已经和前面不同的。之前它的G值是28,并且指向右上方的格子。现在它的G值是20,指向它上方的格子。这在寻路过程中的某处发生,当应用新路径时,G值经过检查变得低了-于是父节点被重新指定,G和F值被重新计算。尽管这一变化在这个例子中并不重要,在很多场合,这种变化会导致寻路结果的巨大变化。那么,我们怎么确定这条路径呢?很简单,从红色的目标格开始,按箭头的方向朝父节点移动。这最终会引导你回到起始格,这就是你的路径!看起来应该像图中那样。从起始格A移动到目标格B只是简单的从每个格子(节点)的中点沿路径移动到下一个,直到你到达目标点。
A*寻路算法

推荐阅读更多精彩内容