赞
踩
假设一个走迷宫游戏,我将 A 点定义成起点也就是开始状态,定义 B 为终点也就是结束状态。我们的目标就是找到从 A 走到 B 地最佳路径,如下图所示:
我们可以将上述的迷宫看做一张图,在简单的情况下(比如这个),生成的图由少量节点和边组成,BFS、DFS 和 Dijkstra 就足够了。然而,在现实生活中,因为我们通常要处理组合复杂性非常大的问题,我们将不得不处理大量的节点和边,而 BFS、DFS 和 Dijkstra 要不可避免地遍历整张图,搜索代价十分巨大。因此,我们必须使用某种意义上的引导算法(启发式算法)。A* 算法就是一种启发式算法。与其他图遍历算法不同,A* 仅根据其功能在看起来有希望且合理的情况下执行一个步骤。它朝着目标前进,不考虑任何非最佳步骤,所以 A* 所搜索出来的路径一定是最优路径。这使得 A* 对于人工智能系统非常有用——尤其是在机器学习和游戏开发中,因为这些系统复制了现实世界的场景。
A* 基于使用 启发式 方法来实现最优性和完整性,是最佳优先算法的一种变体。当搜索算法具有最优性时,这意味着它可以保证找到可能的最佳解决方案。当搜索算法具有完备性时,这意味着如果给定问题的解决方案存在,则该算法保证找到它。
每次 A* 进入一个状态即图中的一个节点,它计算从当前节点移动到所有相邻节点的成本 f(n)
,然后进入具有最低 f(n)
的节点,注意 n
指的是当前节点的邻居节点。计算公式如下:
f
(
n
)
=
g
(
n
)
+
h
(
n
)
f(n) = g(n) + h(n)
f(n)=g(n)+h(n)
其中:
f(n)
:从初始状态经由状态 n
到目标状态的代价估计;g(n)
:在状态空间中从初始状态到当前状态 n
的实际代价;h(n)
:从当前状态 n
到目标状态的最佳路径的估计代价。为了能够重建任何路径,我们需要用具有最佳 f(n)
值的相对标记每个节点。这也意味着如果我们重新访问某些节点,我们也必须更新它们的最佳邻居。A* 的效率高度依赖于启发式值h(n)
,并且根据问题的类型,我们可能需要使用不同的启发式函数来找到最佳解决方案。。
如果一个给定的启发式函数从不高估 n
和目标节点之间的实际距离,则 h(n)
是可接受的。因此,对于每个节点 n
,适用以下公式:
h
(
n
)
≤
h
∗
(
n
)
h(n) \leq h^*(n)
h(n)≤h∗(n)
h*(n)
是 n
和目标节点之间的实际距离。但是,如果函数确实高估了实际距离,但从不超过 d
,我们可以肯定地说,该函数产生的解的精度为 d
(即它不会高估从开始到结束的最短路径超过 d
)。
如果估计总是小于或等于目标节点 n
和其任何邻居之间的估计距离,加上到达该邻居的估计成本,则给定启发式函数h(n)
是一致的:
c
(
n
,
m
)
+
h
(
m
)
≥
h
(
n
)
c(n, m) + h(m) \geq h(n)
c(n,m)+h(m)≥h(n)
如果 h(n)
是一致的,那么我们就知道到任何已经检查过的节点的最佳路径。这意味着这个函数是最优的。
本次实现从零开始,也即先构建一个加权有向图,其次利用 A* 算法寻找图中的某一节点到达另一节点的的最佳路径,其中:
h(n) = 1
,即任何的当前节点到目标节点的最佳路径的估计代价均为1。图的构建如下:
adjacency_list = {'A' : [('B', 1), ('C', 3), ('D', 7)],
'B' : [('D', 5)],
'C' : [('D', 12)]}
代码如下:
adjacency_list = {'A' : [('B', 1), ('C', 3), ('D', 7)],
'B' : [('D', 5)],
'C' : [('D', 12)]}
class Graph() :
def __init__(self, adjacency):
self.adjacency = adjacency
def get_neighbors(self, v):
return self.adjacency[v]
def h(self, n):
H = {'A' : 1,
'B' : 1,
'C' : 1,
'D' : 1}
return H[n]
def A_star(self, start, target):
# 首先说明:下面注释中邻居节点和子节点指的是同一个意思
# open_list 存的是已访问,但该节点的邻居节点仍未被访问的节点,从 start 节点开始
# close_list 存的是已访问,且该节点的邻居节点已被访问的节点
open_list = set(start)
close_list = set()
# g 存的是
g = {}
g[start] = 0
# 记录所有节点的父节点
parents = {}
parents[start] = start
while len(open_list) > 0:
# 当前节点
n = None
# 在当前节点的邻居节点中找到 f() 值最低的节点,更新当前节点
for v in open_list :
if n == None or g[v] + self.h(v) < g[n] + self.h(n) :
n = v
if n == None :
print('path does not exists!')
return None
# 如果当前节点是目标节点,回溯求得最佳路径。利用 parents
if n == target :
path = []
while parents[n] != n :
path.append(n)
n = parents[n]
path.append(start)
path.reverse()
best_path = '->'.join(path)
print(f'the best path of the node {start} to {target} is: {best_path}')
return path
# 当前节点的所有邻居节点
for (m, weight) in self.get_neighbors(n) :
# 如果没被访问过,添加至 open_list
if m not in open_list and m not in close_list :
open_list.add(m)
parents[m] = n
# 开始节点到当前节点的邻居节点的代价,为求下一个最佳节点做准备
g[m] = g[n] + weight
# 否则说明 m 节点的父节点是 n 节点的子节点,也是最佳路径中的上一个节点的子节点
else :
# 如果从上一个节点先访问 n 再访问 m 比直接访问 m 更快,则更新父节点和相应的代价
if g[m] > g[n] + weight :
g[m] = g[n] + weight
parents[m] = n
# 如果 m 节点位于 closed_list 中,将其移至 open_list
if m in close_list :
close_list.remove(m)
open_list.add(m)
open_list.remove(n)
close_list.add(n)
print('path does not exists!')
return None
graph = Graph(adjacency_list)
graph.A_star('A', 'D')
代码中有详细注释,便不再讲解。效果如下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。