揭开A*算法的神秘面纱

一、总结

一句话总结:f(n)=g(n)+h(n)

这个算法有点像BFS的优化算法。

g(n)为起点到当前方格的距离,这个是已知的。

h(n)为当前方格到终点的距离,这个简单点可以直接用曼哈顿距离算。

这个算法就是在bfs的基础上每次取f(n)最小的那个点。

找不到的话就回溯。

1、为什么广度优先算法能找到最优路径,但是却很耗时呢?

层次 路径

因为用层次表示路径

广度优先搜索之所以能找到最优的路径,原因就是每一次扩展的点,都是距离出发点最近、步骤最少的。如此这样递推,当扩展到目标点的时候,也是距离出发点最近的。这样的路径自然形成了最短的路线。

任何事情都有正反两面。正是由于广度优先搜索一层层的扩展,虽然让他找到了最优的路线,但是,他却很傻的走完了绝大多数格子,才找到我们的目标点。也就是,他只关注了当前扩展点和出发点的关系,而忽略了当前点和目标点的距离。如果,如果,如果……我们每扩展一个点,就踮起脚尖,看看诗和远方,找找我们要寻找的那个目标,是不是就有可能指引我们快速的去往正确的方向,而不用傻乎乎的一层层的发展了呢?

2、A*算法的秘诀?

起点距离 目标点距离

A*算法相对广度优先搜索算法,除了考虑中间某个点同出发点的距离以外,还考虑了这个点同目标点的距离。这就是A*算法比广度优先算法智能的地方。也就是所谓的启发式搜索。

我们简单的抽象一下,如果用f(M)表示:从起点S到终点E(经过M点)的距离,那他就可以表示成为两段距离之和,即:S→M的距离 + M→E的距离。如果我们用符号表示的话,就可以写成:f(M) = g(M) + h(M)。

3、A*算法有障碍物的情况?

回溯 分类讨论

可以用回溯

也可以用下面这种情况讨论

1、当估算的距离h完全等于实际距离h'时,也就是每次扩展的那个点我们都准确的知道,如果选他以后,我们的路径距离是多少,这样我们就不用乱选了,每次都选最小的那个,一路下去,肯定就是最优的解,而且基本不用扩展其他的点。

2、如果估算距离h小于实际距离h'时,我们到最后一定能找到一条最短路径(如果存在另外一条更短的评估路径,就会选择更小的那个),但是有可能会经过很多无效的点。

3、如果估算距离h大于实际距离h'时,有可能就很快找到一条通往目的地的路径,但是却不一定是最优的解。

因此,A*算法最后留给我们的,就是在时间和距离上需要考虑的一个平衡。如果要求最短距离,则一定选择h小于等于实际距离;如果不一定求解最优解,而是要速度快,则可以选择h大于等于实际距离。

二、A*算法的百度中简介

A*算法

A*算法,A*(A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法,也是解决许多搜索问题的有效算法。算法中的距离估算值与实际值越接近,最终搜索速度越快。
 
中文名
A*算法
外文名
A-star algorithm
别    称
启发式搜索
表达式
f(n)=g(n)+h(n)

这个算法有点像BFS的优化算法。

g(n)为起点到当前方格的距离,这个是已知的。

h(n)为当前方格到终点的距离,这个简单点可以直接用曼哈顿距离算。

这个算法就是在bfs的基础上每次取f(n)最小的那个点。

三、A*,那个传说中的算法(转)

转自:A*,那个传说中的算法 - CSDN博客
http://blog.csdn.net/zgwangbo/article/details/52078338

今天想跟大家聊的,是我们经常用到,但是却让大家觉得十分神秘的那个算法:A* 。

想必大家都玩儿过对战类的游戏,老王读书那会儿,中午吃完饭就会跟几个好哥们儿一起来两局红警。后来升级了,玩儿星际(是不是暴露年龄了,哈哈~~)。

玩儿的时候,就会发现这里面的兵(为了方便描述,把坦克、飞艇、矿车、龙骑等统称为兵),你只要指定好地点,他们就会自己朝目的地进发,最终去向你指定的地点。不过红警的实现似乎要差一点,经常走绕路,然后在路上就莫名其妙被人干了……

于是,老王就对这个找路算法做了些研究,去查了查资料。所有的资料都一致显示,这些寻路算法,基本上使用的都是一个叫做A*的算法。不过当时看了算法,没有去实践,所以也没有太深入的思考,只是知道他是一种启发式的搜索算法,能够比较快的找到相对优的路径。说来也巧,后来因为百度的A-Star算法比赛进入百度实习,才了解了很多互联网相关的技术。

说在前面的话:因为老王不是做游戏的,游戏的寻路算法肯定有做各种优化,老王只是聊聊自己理解的A*算法,所以讲的不对的地方请专家们指正,专家们切勿生气^_^

好了,背景说完了,我们开始吧~

广度优先(BFS)和深度优先(DFS)搜索

在谈A*之前,还是要先聊聊搜索算法中的老祖宗,深度和广度优先搜索算法。这两个算法,基本上各教科书都会有讲解,各种面试基本上也都会面到。不过为了讲清楚A*,我们还是先一起来看看他们吧。

深度优先搜索,用俗话说就是不见棺材不回头。算法会朝一个方向进发,直到遇到边界或者障碍物,才回溯。一般在实现的时候,我们采用递归的方式来进行,也可以采用模拟压栈的方式来实现。

如下图,S代表起点,E代表终点。我们如果按照右、下、左、上这样的扩展顺序的话,算法就会一直往右扩张,直到走到地图的右边界,发现没找到目标点,然后再回溯。

这个算法的好处就是实现简单,可能就十几行代码。不过问题也很明显,就是:

1、路径可能不是最优解;

2、寻路时间比较长。

广度优先搜索,这个用形象的比喻,就像是地震波,从起点向外辐射,直到找到目标点。我们在实现的时候,一般采用队列来实现。

这个算法的优点:

1、简单。代码也就几十行;

2、路径能找到最优解;

不足:

1、算法消耗的时间比较大,遍历的点会很多。

这里就引出一个问题:为什么广度优先算法能找到最优路径,但是却很耗时呢?

A*算法

广度优先搜索之所以能找到最优的路径,原因就是每一次扩展的点,都是距离出发点最近、步骤最少的。如此这样递推,当扩展到目标点的时候,也是距离出发点最近的。这样的路径自然形成了最短的路线。

任何事情都有正反两面。正是由于广度优先搜索一层层的扩展,虽然让他找到了最优的路线,但是,他却很傻的走完了绝大多数格子,才找到我们的目标点。也就是,他只关注了当前扩展点和出发点的关系,而忽略了当前点和目标点的距离。如果,如果,如果……我们每扩展一个点,就踮起脚尖,看看诗和远方,找找我们要寻找的那个目标,是不是就有可能指引我们快速的去往正确的方向,而不用傻乎乎的一层层的发展了呢?

我们来看看下图:

同样是从出发点S走了两步以后到达的M1和M2两个点,如果让你来选择,你会选择他们中的谁来做扩展点呢?很明显,只要是眼力不差的人,都会选择M1。为什么呢?因为M2需要再走9步,才能到达终点E;而M1只需要7步!!!

注意了!我们的判断依据,除了考虑了中间这个点同出发点的距离以外,还考虑了这个点同目标点的距离,对吧~

如果你想到了这一点,恭喜你,你已经掌握了A*算法的秘诀了:A*算法相对广度优先搜索算法,除了考虑中间某个点同出发点的距离以外,还考虑了这个点同目标点的距离。这就是A*算法比广度优先算法智能的地方。也就是所谓的启发式搜索。

我们简单的抽象一下,如果用f(M)表示:从起点S到终点E(经过M点)的距离,那他就可以表示成为两段距离之和,即:S→M的距离 + M→E的距离。如果我们用符号表示的话,就可以写成:f(M) = g(M) + h(M)。

怎么样,看起来这个公式是否是很简单呢?

我们扩展到M点的时候,S→M的距离就已经知道,所以g(M)是已知的。但是M到E的距离我们还不知道。如果我们能用某种公式,能大概预测一下这个距离,而这个预测的值又比较精确,我们是不是就能很精确的知道每一个即将扩展的点是否是最优的解路径上的点呢?这样找起路来,是不是就很快呢?

所以,接下来最关键的问题,就是怎么计算这个h(M)的值!

可能大家都会问一个问题:从M→E的距离不是很好计算嘛?用横向的距离+纵向的距离就完了!

这个问题问的很好,但是结论是:既对,又不对。如果按照我们之前的图来看,这个结论是正确的。但是,如果是下面这张图呢?

在M和E之间,有一堵蓝色的墙,这个时候,M→E的距离,还是横向的直线距离 + 纵向的直线距离嘛?明显不是了,他需要绕道!

这个时候,似乎希望破灭了……

前两天有个朋友给我说,两口子的相处之道,就是相互包容,不要太较真儿。如果我们将这个思想用到这里,把h(M)看做一个估计的值,而不是精确值,那问题是不是就解决了呢?

也就是说,我们尽可能找那些f(M)=g(M)+h(M)小的点(其中h(M)是个估算值),当做我们的路径经过点,即使实际的h'(M)值可能和h(M)值不等也没关系,我们就当做一个参考(总比广度优先搜索好吧~)。如果通过这个估算,能干掉很多明显很差的点,我们也就节省了很多不必要的花销,也算赚到了,对吧~

比如,上图中, M点即使是绕路,也比M'点要强,对吧。在估算的时候,我们就可以将S左边的点基本上都抛弃掉,从而减少我们扩展的点数,节约计算的时间。

说完上面的东东,我们大面儿上的东西就说的差不多了,接下来就省两个问题要去解决了:

1、这个估算的函数h(M)怎么样去计算?

2、对于不同的估算函数h(M)来讲,对于我们的搜索结果会有什么样的影响?

那我么一个个的来回答吧。

估算函数h(M)如何计算?

常见的距离计算公式有这么几种:

1、曼哈顿距离:这个名字听起来好高端,说白了,就是上面我们讲的横向格子数+纵向格子数;

2、欧式距离:这个名字听起来也很高端,说白了,就是两点间的直线距离sqrt((x1-x2)2 + (y1-y2)2)

除了上述的距离计算公式以外,还有一些变种的距离计算公式,如:对角线距离等等。这个就在具体的问题中做具体的优化了。

不同估算函数对于结果的影响

那距离公式选择不同,对我们的寻路结果有哪些影响呢?

1、当估算的距离h完全等于实际距离h'时,也就是每次扩展的那个点我们都准确的知道,如果选他以后,我们的路径距离是多少,这样我们就不用乱选了,每次都选最小的那个,一路下去,肯定就是最优的解,而且基本不用扩展其他的点。如下图:

2、如果估算距离h小于实际距离h'时,我们到最后一定能找到一条最短路径(如果存在另外一条更短的评估路径,就会选择更小的那个),但是有可能会经过很多无效的点。极端情况,当h==0的时候,最终的距离函数就变成:

f(M)=g(M)+h(M)

=> f(M)=g(M)+0

=> f(M)=g(M)

这不就是我们的广度优先搜索算法嘛?! 他只考虑和起始点的距离关系,毫无启发而言。

3、如果估算距离h大于实际距离h'时,有可能就很快找到一条通往目的地的路径,但是却不一定是最优的解。

因此,A*算法最后留给我们的,就是在时间和距离上需要考虑的一个平衡。如果要求最短距离,则一定选择h小于等于实际距离;如果不一定求解最优解,而是要速度快,则可以选择h大于等于实际距离。

好了,口水话讲了这么多,来看代码吧。老王粘贴了最核心的那段代码,如下:

完整的代码请参见老王的github:

https://github.com/simplemain/astar

老王定义了一张地图:

当用以下距离公式计算h值的时候,效果如图:

1、曼哈顿距离:

很明显,大部分的空白点都没有去遍历,而且最终找到了最优的路径。

2、欧式距离:

同曼哈顿距离一样,效果差不多,不过多扩展了几个点。

3、欧式距离的平方

这种情况就是h值大于等于实际距离的,明显他扩展的点很少,不过找到的路径却不是最短路径。

4、BFS的情况(h值恒为0)

这种算法基本等同于BFS,所有点基本都被扩展了,但是还是找到了最优的那个路径。

参考:

A*,那个传说中的算法 - CSDN博客
http://blog.csdn.net/zgwangbo/article/details/52078338

揭开A*算法的神秘面纱的更多相关文章

  1. 【转】再讲IQueryable<T>,揭开表达式树的神秘面纱

    [转]再讲IQueryable<T>,揭开表达式树的神秘面纱 接上篇<先说IEnumerable,我们每天用的foreach你真的懂它吗?> 最近园子里定制自己的orm那是一个 ...

  2. 揭开webRTC媒体服务器的神秘面纱——WebRTC媒体服务器&开源项目介绍

    揭开webRTC媒体服务器的神秘面纱--WebRTC媒体服务器&开源项目介绍 WebRTC生态系统是非常庞大的.当我第一次尝试理解WebRTC时,网络资源之多让人难以置信.本文针对webRTC ...

  3. 揭开HTTP网络协议神秘面纱系列(三)

    HTTP首部字段有四种类型:通用首部字段,请求首部字段,响应首部字段,实体首部字段. 通用首部字段: 首部字段 说明 Cache-Control 控制缓存的行为 Connection 逐跳首部.连接的 ...

  4. 再讲IQueryable<T>,揭开表达式树的神秘面纱

    接上篇<先说IEnumerable,我们每天用的foreach你真的懂它吗?> 最近园子里定制自己的orm那是一个风生水起,感觉不整个自己的orm都不好意思继续混博客园了(开个玩笑).那么 ...

  5. 揭开SQL注入的神秘面纱PPT分享

        SQL注入是一个老生常谈但又经常会出现的问题.该课程是我在公司内部培训的课程,现在分享出来,希望对大家有帮助.     点击这里下载.

  6. 揭开HTTP网络协议神秘面纱系列(二)

    HTTP报文内的HTTP信息 HTTP协议交互的信息被称为HTTP报文,请求端的HTTP报文叫做请求报文,响应端的叫做响应报文. HTTP为了提升传输速率,其在传输数据时,按照数据原样进行压缩传输,相 ...

  7. 揭开HTTP网络协议神秘面纱系列(一)

    1.了解Web及网络基础 TCP/IP协议族按层次可以分为下面四层: 应用层:决定了向用户提供应用服务时通信的活动,TCP/IP协议族内预存了各类通用的应用服务,比如:FTP(文件传输协议)和DNS( ...

  8. 揭开yield关键字的神秘面纱

    写在前言 经常会看见,python函数中带有yield关键字,那么yield是什么,有什么作用? 答案:可以理解yield是一个生成器: 作用:遇到yield关键字,函数会直接返回yield值,相当于 ...

  9. 1.揭开消息中间件RabbitMQ的神秘面纱

    当你看到这篇博文的时候,相信你至少已经知道RabbitMQ 是一个非常优秀的消息中间件,它使用专门处理高并发的Erlang 语言编写而成的消息中间件产品. 当然如果你不知道也没关系,读完本篇你将Get ...

随机推荐

  1. Loadrunner中参数化取值方式分析

    Loadrunner中参数化取值依赖两个维度: 1.取值顺序分为“顺序”“随机”“唯一”.    select next row:Sequential , Random,unique 2.更新值时分为 ...

  2. centos7命令1

    ls  查看当前路径下的文件或文件夹 pwd 查看当前路径,例如/home/python   表示根目录下的home文件夹下的python文件夹 clear清空屏幕 /斜杠 \反斜杠 |竖杠 _下划线 ...

  3. ORM到底是用还是不用?(复制)

    ORM即Object/Relation Mapping的简写,一般称作“对象关系映射”,在Web开发中最常出没于和关系型数据库交互的地方.接口.中间件.库.包,你都可以这么称呼它.ORM我们可以结合P ...

  4. Tomcat内部结构及请求原理(转)

    Tomcat Tomcat是一个JSP/Servlet容器.其作为Servlet容器,有三种工作模式:独立的Servlet容器.进程内的Servlet容器和进程外的Servlet容器. Tomcat的 ...

  5. iOS 个人所得税 app 基础解析实践

    前言:2019年 新个税实施在即,全国几乎所有在职员工都会下载“个人所得税”app来使用,并且 注册使用过程需要填写身份证号等相当私密重要的个人信息. 至今,各大app平台应用下载榜首仍然“无人能出其 ...

  6. Scala List 用法

    1.++[B]   在A元素后面追加B元素 scala> val a = List(1) a: List[Int] = List(1) scala> val b = List(2) b: ...

  7. doc命令下查看java安装路径

    在doc窗口下使用命令:set  java_home 即可查看.

  8. oracle中job定时器任务

    对于DBA来说,经常要数据库定时的自动执行一些脚本,或做数据库备份,或做数据的提炼,或做数据库的性能优化,包括重建索引等等的工作.但是,Oracle定时器Job时间的处理上,千变万化,今天我把比较常用 ...

  9. 【c++ primer, 5e】特殊用途语言特性

    [默认实参] 1.注意点:函数的默认实参可以在函数的声明中添加,但是后续声明只能添加默认参数而不能改变先前声明的默认参数.(函数的声明通常是定义在头文件上的,多次声明同一个函数是合法的) 2.默认实参 ...

  10. ArrayList原理分析(重点在于扩容)

    首先,ArrayList定义只定义类两个私有属性: /** * The array buffer into which the elements of the ArrayList are stored ...