2021-04-29

浅析A星算法

A星算法主要用于求起始点到目标点的最短路径问题(此问题的前提是两点之间一定有障碍物),最短路径问题在网络通信的路由选择及导航等中有着广泛的应用。

文章目录

  • 前言
  • 一、欧几里得距离(Euclidean Distance)和曼哈顿距离(Manhattan Distance)
  • 算法
  •        1.Dijkstra算法
  •        2.BFS算法(广度优先算法)
  •        3.A*算法及详细示范
  •        4.部分改进
  • 总结
  • 注释

 


前言

A*算法,别称“启发式搜索”,要了解A星算法,我们应该先搞清楚欧几里得距离和曼哈顿距离。A*算法可以说是两种算法的优势和(Dijkstra with a Heurist),所以接着我们继续探究其组成——BFS算法和Dijkstra算法,这有利于我们对A*算法的理解。

 

一、欧几里得距离和曼哈顿距离

a.欧几里得度量(euclidean metric)(也称欧氏距离)是一个通常采用的距离定义,指在m维空间中两个点(起始点到目标点)之间的真实距离,或者向量的自然长度(即该点到原点的距离)。. 在二维和三维空间中的欧氏距离就是两点之间的实际距离,可以理解为向量的模。

b.出租车几何或曼哈顿距离(Manhattan Distance)是由十九世纪赫尔曼·闵可夫斯基所创词汇 ,是种使用在几何度量空间的几何学用语,用以标明两个点在标准坐标系上的绝对轴距总和。

 为什么叫出租车几何呢?这是因为街道纵横交错,从起始点到目标点不可能走直线到达,而是通过道路到达,这个路程值就称为曼哈顿距离(x值与y值的绝对值之和)。在这里插入图片描述

该图中绿线为欧式距离,红线为曼哈顿距离,黄蓝线为等价曼哈顿距离

曼哈顿距离是|x|值和|y|值的和,无论走什么路线,最后的计算值都是不变的,曼哈顿距离不是距离不变量,当坐标轴变动时,点间的距离就会不同,由于这只涉及加减计算,可以节约宝贵的算力,而欧几里得距离进行浮点计算,对算力的需求较大。在距离计算中,我们一般将每个方格的边长设为10,对角线的长度为14(约等于10x1.41),这样方便计算,且误差较小。

 

 

 

黄线:欧几里得距离

绿线:曼哈顿距离

 

二、算法

在讨论算法之前,我们先建立open-closed表,即一个open-list和closed-list。将open-list理解为一个容器(例如队列或是链表),这里面的每一个项都是待检查的;closed-list同理,不过这里面的每一项都是检查过的,在下一步检察中这些项都不会被检查。

2.1 Dijkstra算法

Pros搜索的完整性和最佳性
Cons

只能看到到目前为止积累的成本(即统一成本),从而在每一个"方向"上探索下一个状态 ;没有关于目标位置的信息

Dijkstra算法从物体所在的初始点开始,访问图中的结点。它迭代检查待检查结点集中的结点,并把和该结点最靠近的尚未检查的结点加入待检查结点集(open-list)。从初始结点向四面八方扩展搜索(in all directions),直到到达目标结点。Dijsktra算法保证能找到一条从初始点到目标点的最短路径,只要所有的边都有一个非负的代价值。(上文说过方格边长一般取10)


2.2 BFS算法

最佳优先搜索(BFS)算法按照类似的流程运行,它能够评估任意点到目标点的代价,在搜索中,它会选择离目标点较近的点,也就说,如果目标点在南方向,它会倾向于导向南方的路径。

但是,由于其仅考虑到达目标点的代价,而忽略了以及花费代价,所以在某些情况下(如有障碍物)尽管路径变得很长,它仍然继续走下去。与其相似的还有DFS(深度优先算法),这里不做探讨,感兴趣的读者可以百度查询。

 


2.3 A*算法

A*算法(又称“启发式搜索”)于1968年发明,它是将BFS(贪心算法)和Dijsktra算法结合在一起的算法。

在正式讨论之前,我们先确定三个函数式:f(n),g(n)和h(n);得以下公式:

在这里插入图片描述

f(n)是评估函数,G值是从起始点到现在所在点m(current node)的移动代价,这是一个叠加的值,也就是说下一个节点的G值是其前继G值加上前继到该点的移动代价值;H值是从现在的方格m移动到终点 的估算成本。H值我们一般使用曼哈顿距离进行测算。这里我们要注意,G值和H值的衡量单位要保持一致,否则你可能无法得到正确的路径,而且算法也会运行的更加慢!

 

下面是关于三种算法的一个说明H值与G值的关系):

  • 1.如果H值=0,那么只有G值是有用的,这时A*算法也就是Dijkstra算法,它能保证可以找到一条最优路径。
  • 2.如果H值总是小于(或等于)从现在节点走到目标结点的步数,那么A*算法一定可以找到最优路径. H值越小,A*扩展的结点就越多,这会导致A*算法变得越慢。
  • 3.如果H值恰好等于从现在节点走到目标结点的步数,A*算法扩展的所有结点都在最优路径上,它不会扩展任何其他无关结点,此时A*算法的速度是非常快的.尽管你无法总是做到这一点,但在某     些情况下你可以使他这么运行。
  • 4.如果H值所给出的信息有时大于从结点n走到目标结点的步数,那么A*算法将无法确保能够找到最优路径,但它会运行得更快。
  • 5.在极端的情况下,如果H值十分接近G值,那么只有H值起作用,此时A*算法实际上变成BFS。

 

    第一.

      现确定搜索的区域,假设起始点(start node)是A点,目标点(target node)是B点,中间是一堵墙(障碍,obstacles),它们分别用绿,红,蓝色表示。障碍的意思就是该方格无法通行,黑色的方格是可通行的。路径就是方格中心点移动产生的轨迹线。

   

   第二.

1.  从起点 A 开始,并把它就加入到一个由方格组成的 open list中。现在 open list 里只有一项,它就是起点 A ,后面会慢慢加入更多的项。 Open list 里的格子是路径可能会是沿途经过的,也有可能不经过的。

2.  查看与起点 A 相邻的方格 ( 忽略障碍所处的方格,若周围方格无障碍,则有8个相邻方格,把其中可走的 (walkable方格也加入到 open list 中。把起点 A 设置为这些方格的父节点 (parent node。当我们在追踪路径时,这些父节点的内容是很重要的。

3.   A  open list 中移除,加入到 close list中, close list 中的每个方格都是现在不需要再关注的,上文已经说过。

如下图所示,深绿色的方格为起点,它的外框是亮蓝色,表示该方格被加入到了 close list 。与它相邻的黑色方格是需要被检查的,他们的外框是亮绿色。每个黑方格都有一个灰色的指针指向他们的父节点,这里是起点 A 


下一步,我们需要从 open list 中选一个与起点 A 相邻的方格,按下面描述的一样或多或少的重复前面的步骤,被选中的方格应该具有最小的F值,通过计算子节点的F,G,H,值我们得到下图:

显然,正右方的方格具有最小的F值,以该点为例,我们来计算一次其三个值。该点中心到目标点的距离是3,由曼哈顿法得,|x|=30.|y|=0,则H值(曼哈顿距离)为30;G值为起始点到该点的实际距离为10,右下方的方格G值为对角线的长度14,H值相应的算出为40(有多种移动方法,但结果是一样的)。

 

    第三.

接下来,,我们 open list 中选择 F 值最小的 ( 方格 ) 节点,然后对所选择的方格作如下操作:

1.把它从 open list 里取出,放到 close list 中,表明它已经被检查了。

2.检查它相邻的所有方格,忽略在 close list 中或是障碍方格 ,若周围方格不在open lsit 中,将它们加入到 open list 中。同时把我们选定的F值最小的新方格(current node)设置为这些新加入的方格的父节点。

3. 如果某相邻的方格m已经在 open list 中,检查从起始点经过当前方格到达那个m方格是否具有更小的 G 值。如果有,则把那个方格m的父亲设为当前方格 (current node ) ,然后重新计算那个方格m F ,G 值和H值。相反,不做任何操作。 接下来,我们以下图为例进行一个示范:

 



详细示范!!!  示范了一个完整的步骤:

 

这次,当我们检查相邻的方格时,我们发现它右边的方格是墙,忽略墙在的方格,我们把墙下面的一格也忽略掉。因若不穿越墙角的话,你无法直接从当前方格移动到那个方格。你需要先往下走,然后再移动到那个方格以来绕过墙角。 ( 注意:穿越墙角的规则是可选的,这里我们设定其不可穿过 )

这样还剩下 5 个相邻的方格。current code下面的 2 个方格还没有加入 open list ,所以把它们加入,同时把当前方格设为他们的父节点。在剩下的个方格中,有 2 个已经在 close list  ( 一个是起点,一个是当前方格上面的方格,外框被加亮的 ) ,我们忽略它们。最后一个方格,也就是当前方格左边的方格,我们检查经由当前方格到达那里是否具有更小的 G 值,没有,因此我们准备从 open list 中选择下一个待处理的方格。

不断重复这个过程,直到把终点也加入到了 open list ,此时如下图所示:

注意观察各个图中方格的父节点和指针指向,这里我们要利用这些得出最短路径图,从终点开始,按着指针向父节点移动,当你被带回到起点时所产生的轨迹就是你的路径。如下图所示:

注:这里我们要注意父节点的问题,观察上图可知起始点下方两方格的父节点和指针指向发生了变化,原来的G是28,变化后是20,这是在获得新路径时发生的,因为G值变得不同了,所以其父节点,F和G值都要重新测定,这是一个重要的细节!


下面是A*算法的伪代码:

A*算法

4.小小的改进

尽管A*算法综合了两种算法的优势,但是仍然性能较低,低性能的一个原因来自于启发函数的约束或者说不足(原文是ties)。当某些路径具有相同的f值的时候,它们都会被搜索(explored),尽管我们只需要搜索其中的一条,与Dijkstra算法相比这仍然需要大量的算力,尤其是地图较大的时候:

解决这个问题,我们可以为启发函数添加一个附加值small tie breaker)。附加值对于结点必须是确定性的,也就是说,它不能是随机的数,而且它必须让f值体现区别。因为A*f值排序,f值差异意味着只有一个等效的f值会被检测。下图是改进后的图像:


总结

我们总结一下A*算法的步骤

1. 将起点加入 open list 中。

2. 重复以下过程:

a. 查看 open list 查找 F 值最小的节点,并把它作为当前将处理的节点(current code)。

b.  把这个节点加入到 close list 中。

c.   对当前方格的 8 个相邻方格的处理:

     如果它是障碍(obstacles)或者它在 close list 中,忽略它。否则,做如下操作。:  如果它不在 open list 中,把它加入 open list ,并且把当前方格设置为它的父亲,测算该方格的 F  G  H ; 如果它已经在 open list 中,检查这条路径 ( 即从起始点经过当前方格到达它那里 ) 是否更好,用 G 值作参考。更小的 G 值表示这是更好的路径。如果是这样,把它的父节点设置为当前方格,同时重新计算它的 G  F 值。

d. 暂停,当你出现以下情况时:终点被加入到了 open list 中,此时已经找到了路径; 查找终点失败,并且 open list 是空的,此时不存在路径。

 3.  保存路径。从终点开始,每个方格中心点沿着父节点(指针方向)移动直至起始点,形成的轨迹就是我们要寻找的路径。 

 



 

注释:

1.图片来源于CSDN社区的主流A*算法博客,原作者是Patrick Lester,点击链接阅读原文:http://www.gamedev.net/reference/articles/article2003.asp

2.参考博客来源:

       a.https://blog.csdn.net/coutamg/article/details/53923717    这是一篇译文  原作链接:http://theory.stanford.edu/~amitp/GameProgramming/  (作者Amit),讲解的很详细,读者想了解更多可以阅读他们的文章。 

       b.https://eveture.blog.csdn.net/article/details/89382502   这篇博客是翻译Patrick Lester的文章,笔者认为这篇翻译的相对好一些。

 


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值