0 引言
搜索算法主要可以分为以下两类:
1.盲目搜索算法;如深度优先搜索(DFS)、广度优先搜索(BFS)和Dijkstra算法等,这些算法不使用任何额外信息,只是简单地按照一定的顺序遍历所有结点。
2.启发式搜索算法;如贪婪最佳优先搜索算法(GBFS)、A* 算法。这些算法使用启发函数来估算每个结点到目标结点的距离,并以此为依据优先遍历结点。A* 算法在游戏智能、地图导航和机器人路径规划等领域有广泛的应用,因此很有必要掌握A* 算法。
1 A*算法的数学原理
1.1 启发函数和评价函数
A* 算法作为一种启发式搜索算法,自然满足启发式搜索的定义。对于任意一个启发式搜索算法,都具有启发函数 h ( n ) h(n) h(n)和评价函数 f ( n ) f(n) f(n)。对搜索结点的选取主要取决于对应结点的评价函数 f ( n ) f(n) f(n), f ( n ) f(n) f(n)决定了搜索算法可扩展结点 n n n的优先度。一般规定,在边缘集合中所有备选的结点中,评价函数的值越小,则其被选择的优先级越高。举例:对于BFS,评价函数就是当前结点的深度;对于DFS,评价函数就是当前结点深度的倒数。
1.2 贪婪最佳优先搜索算法(GBFS)
基于启发函数和评价函数,一种自然的想法便是我每次都在边缘结点集合中选取离目标结点最近的那个结点进行探索即可。用数学化的语言表示为:
f
(
n
)
=
h
(
n
)
f(n)=h(n)
f(n)=h(n)。
h
(
n
)
h(n)
h(n)为结点的启发函数值,表示结点
n
n
n到达目标结点所付出的代价。启发函数用于评估当前结点距离目标结点的距离,有非常多的选择,一般通过距离进行衡量,如曼哈顿距离、欧式距离等,也可以通过其他方式来进行选择设计。
GBFS算法的致命缺陷就是:算法总是从当前结点出发,只考虑当前结点到目标结点的代价,而没有考虑初始结点到目标结点的代价,即忽略了已经搜索过的路径长度,丢失了历史信息,只考虑了局部信息。
GBFS搜索算法示例如下:
2 A*算法
为了解决GBFS算法的局限性,我们从评价函数出发,引入能够表示从起始节点到达当前节点
n
n
n的路径代价函数
g
(
n
)
g(n)
g(n),修改评价函数
f
(
n
)
=
g
(
n
)
+
h
(
n
)
f(n)=g(n)+h(n)
f(n)=g(n)+h(n),即为A* 算法。此时评价函数的实际含义是:从起始结点到当前所处结点
n
n
n的实际代价(先验)和从
n
n
n到目标结点的后续代价估计(后验)之和。通过这种改进,避免了GBFS算法的极端性,得到了更加智能的A* 算法。
A* 算法的完备性和最优性取决于搜索问题和启发函数的性质。
一个良好的启发函数应满足可容性和一致性。
引入如下符号:
符号 | 说明 |
---|---|
h ( n ) h(n) h(n) | 当前结点 n n n的启发函数值 |
h ∗ ( n ) h^*(n) h∗(n) | 从结点 n n n出发到目标结点的最小代价 |
c ( n , a , n ′ ) c(n,a,n^{'}) c(n,a,n′) | 从结点 n n n执行动作 a a a到达结点 n ′ n^{'} n′的单步代价 |
·可容性:
h
(
n
)
<
h
∗
(
n
)
h(n)<h^*(n)
h(n)<h∗(n)即启发函数不会高估计
·一致性:
h
(
n
)
≤
c
(
n
,
a
,
n
′
)
+
h
(
n
′
)
h(n)\leq c(n,a,n^{'})+h(n^{'})
h(n)≤c(n,a,n′)+h(n′)即三角原则
结论:启发函数可容,则树搜索的A* 算法满足最优性。
如果A* 算法采用图搜索,且A* 算法要继续具有最优性,可以通过对图搜索算法进行修改或者保证启发函数具有一致性来实现。
A*搜索算法示例如下:
3 基于python实现A*算法解决寻路问题
核心算法步骤如下:
def process(self):
# 初始化open集合,并把起始点放入
Edge_Nodes = deque()#边缘结点集合
self.tree = Node(self.startPoint)#树结构存储初始结点
Edge_Nodes.append(self.tree) # 边缘集合初始化从根结点探索
# 没有找到终止结点并且还有未被探索的节点
while self.foundEndNode == None and len(Edge_Nodes) != 0:
node = self.A_star_pick_from(Edge_Nodes) # pick_from 搜索算法核心
neighbors = self.getNeighbors(node.pos)#探索结点附件的子结点
for neighbor in neighbors:
childNode = Node(neighbor)
childNode.frontNode = node # 子结点类记录父节点信息
childNode.calc_f(self.endPoint) # 计算子结点的评价函数值
node.childNodes.append(childNode) # 父结点类添加子结点信息
Edge_Node.append(childNode) # 边缘集合添加探索得到的结点
if neighbor == self.endPoint:# 是否到达目标状态
self.foundEndNode = childNode