A*算法不同于一般搜索算法就是其有一个估价函数 f(x). 每个节点的f(x) = g(x) + h(x)。g(x)是对从初始节点到当前节点所需最小费用的估计,h(x)是从当前节点到目标节点的所需最小费用估计(两个都是估计,前者偏大,后者偏小)。我们每扩展出来一个节点后都要计算其在当前状态下的f值, 其中h值只于当前状态有关,而g值则与当前节点是如何扩展来的有关(一般就是父节点深度+1)。任意一节点h(x) < h*(x), (h*(x)是准确值), 是A*算法的关键。
我们来看下伪代码:
代码
void Astar()
{
将初始节点插入Open表
while(Open表不为空)
{
从Open表中取f值最小的节点a。
扩展节点a:
对每个扩展出来的节点n
{
if( n是目标节点) {输出最小路径长度f(n) return;}
else
{
计算n的f值;
if(n已经在open表中){用当前f值进行较小更新}
else if(n已经在Closed表中)
{
用当前f值进行较小更新
if(更新成功) //要命的操作
{
把n从Closed表中删除,加入Open表中。
}
}
else if(n没有出现在两个表中)
{
将n插入open表;
}
}
}
将a放入closed表
}
}
A*算法能保证如果最优解(最短路径)存在,则找到的解一定是最优解。它的证明基于以下这个性质。
- 如果最短路径长度为C*, 则在算法结束前,open表中至少有一个节点n, 满足 f(n) <= C*.
这个性质可以这样理解, 因为最短路径存在, 我们不妨设它为: source->a->b->c->...->n->.....->goal. 且在当前时刻,路径中在节点n前的节点都在closed表中,即已经扩展了,而节点n自己在open表中(注意:算法结束前任意时刻都有这样的节点n存在)。 则由于该条路劲是最短路径,我们可以知道此时在open表中的n的 g(n)值已经是准确值, 即最小值了。而 f(n) = g(n) + h(n) = g*(n) + h(n) <= g*(n) + h*(n) = C* . (最后一个式子取等号是由于n在最短路径上)
有了这个性质,我们就知道,当A*算法扩展到目标节点时,必有f(goal) = g(goal) <= C* (即 = C*)。否则, 如果f(goal) > C*,由于目标节点是被扩展节点, 则open表中其他任意其他节点t, 都有f(t) >= f(goal) > C*, 和性质1 矛盾。
下面我们来讨论一下h函数的相容性 ,由DFS和BFS算法我们知道,扩展新节点时很容易出现重复节点的问题,从上面的伪代码可以看出, 如果新扩展节点已经存在于closed表中, 且f值比表中节点的f值还要小的话,则除了更新该节点f值,还需要重新扩展该节点,这简直就是把人从棺材里拖出来。 但是如果h函数满足相容性,这一步就可以省掉了。所谓相容性就是指对任意节点s1,都满足:
h(s1) <= h(s2) + c(s1,s2) (其中c(s1,s2)是指从s1转移到s2的代价)
有这个性质我们在不等号两边加上g(s1), 则有 g(s1) + h(s1) <= h(s2) + g(s1) + c(s1,s2)。 如果我们此时扩展s1, 而s2又是能被s1扩展的节点,则由这个式子我们得到 f(s1) <= f'(s2). (若s2之前就已经被扩展出了,则当前的f(s2)可能比f'(s2)小) 这个式子的意义在于由当前节点进行扩展这个方案下得到的节点的f值总比当前扩展节点的f值大(子节点总比父节点费用高),而我们每次又是选择一个具有最小f值的节点进行扩展,然后让其进入closed表,这就使得,进入closed表的每个节点的f值是递增的,并且之后不可能出现比closed表中最大f值还要小的节点被扩展出来(感觉有点问题),因此扩展出的新节点不必再拿到closed表中检查更新了。

浙公网安备 33010602011771号