1 什么是A*算法
假设一个走迷宫游戏,我将 A 点定义成起点也就是开始状态,定义 B 为终点也就是结束状态。我们的目标就是找到从 A 走到 B 地最佳路径,如下图所示:
我们可以将上述的迷宫看做一张图,在简单的情况下(比如这个),生成的图由少量节点和边组成,BFS、DFS 和 Dijkstra 就足够了。然而,在现实生活中,因为我们通常要处理组合复杂性非常大的问题,我们将不得不处理大量的节点和边,而 BFS、DFS 和 Dijkstra 要不可避免地遍历整张图,搜索代价十分巨大。因此,我们必须使用某种意义上的引导算法(启发式算法)。A 算法就是一种启发式算法。与其他图遍历算法不同,A 仅根据其功能在看起来有希望且合理的情况下执行一个步骤。它朝着目标前进,不考虑任何非最佳步骤,所以 A 所搜索出来的路径一定是最优路径。这使得 A 对于人工智能系统非常有用——尤其是在机器学习和游戏开发中,因为这些系统复制了现实世界的场景。
1.1 A* 的基本概念
A 基于使用 启发式 方法来实现最优性和完整性,是最佳优先算法的一种变体。当搜索算法具有最优性时,这意味着它可以保证找到可能的最佳解决方案。当搜索算法具有完备性时,这意味着如果给定问题的解决方案存在,则该算法保证找到它。
每次 A 进入一个状态即图中的一个节点,它计算从当前节点移动到所有相邻节点的成本 f(n)
,然后进入具有最低 f(n)
的节点,注意 n
指的是当前节点的邻居节点。计算公式如下:
其中:
f(n)
:从初始状态经由状态n
到目标状态的代价估计;g(n)
:在状态空间中从初始状态到当前状态n
的实际代价;h(n)
:从当前状态n
到目标状态的最佳路径的估计代价。
为了能够重建任何路径,我们需要用具有最佳 f(n)
值的相对标记每个节点。这也意味着如果我们重新访问某些节点,我们也必须更新它们的最佳邻居。A* 的效率高度依赖于启发式值h(n)
,并且根据问题的类型,我们可能需要使用不同的启发式函数来找到最佳解决方案。。
1.2 启发函数的可接受性和一致性
如果一个给定的启发式函数从不高估 n
和目标节点之间的实际距离,则 h(n)
是可接受的。因此,对于每个节点 n
,适用以下公式:
h*(n)
是 n
和目标节点之间的实际距离。但是,如果函数确实高估了实际距离,但从不超过 d
,我们可以肯定地说,该函数产生的解的精度为 d
(即它不会高估从开始到结束的最短路径超过 d
)。
如果估计总是小于或等于目标节点 n
和其任何邻居之间的估计距离,加上到达该邻居的估计成本,则给定启发式函数h(n)
是一致的:
如果 h(n)
是一致的,那么我们就知道到任何已经检查过的节点的最佳路径。这意味着这个函数是最优的。
2 Python 实现 A* 算法
本次实现从零开始,也即先构建一个加权有向图,其次利用 A* 算法寻找图中的某一节点到达另一节点的的最佳路径,其中:
- 图的存储结构是邻接列表,可参考:Python 中的图:图的存储结构;
- 为了方便,本次定义的启发函数
h(n) = 1
,即任何的当前节点到目标节点的最佳路径的估计代价均为1。
图的构建如下:
1 | adjacency_list = {'A' : [('B', 1), ('C', 3), ('D', 7)], |
代码如下:
1 | adjacency_list = {'A' : [('B', 1), ('C', 3), ('D', 7)], |
代码中有详细注释,便不再讲解。效果如下: