之前发现自己对A*的理解存在问题,在此重新更新一下博文
此处借鉴了一篇神文。
http://www.redblobgames.com/pathfinding/a-star/implementation.html
我也顺便意识到了python代码是一个比伪代码还容易看懂的东西。
在学习A*算法之前,首先回忆一下一个非常经典的单源最短路算法Dijkstra
1)维护一个表dist,储存当前求出的各点到S的距离
2)取出dist表中的最小值(显然这个过程是可以用堆优化的),并用该最小值对其他各点的dist值做松弛更新
3)重复2)过程,直到取出的最小值对应节点为T
让我们来看一下Dijkstra的python实现
1 def dijkstra_search(graph, start, goal): 2 frontier = PriorityQueue() 3 frontier.put(start, 0) 4 came_from = {} 5 cost_so_far = {} 6 came_from[start] = None 7 cost_so_far[start] = 0 8 9 while not frontier.empty(): 10 current = frontier.get() 11 12 #Early Exit! 13 if current == goal: 14 break 15 16 for next in graph.neighbors(current): 17 new_cost = cost_so_far[current] + graph.cost(current, next) 18 if next not in cost_so_far or new_cost < cost_so_far[next]: 19 cost_so_far[next] = new_cost 20 priority = new_cost 21 frontier.put(next, priority) 22 came_from[next] = current 23 24 return came_from, cost_so_far
在经过 启发式Breadth First Search 和 Dijkstra 的比较以后,我们发现,Dijkstra不容易被误导但像无头苍蝇一样乱撞,启发式广搜很有目的性但容易被误导撞墙
所以我们在Dijkstra的基础上引入启发式的思想,把之前以“cost_so_far[p]越小优先级越高”改进为“f[p]越小优先级越高",代码上的修改非常简单
def heuristic(a, b): (x1, y1) = a (x2, y2) = b return abs(x1 - x2) + abs(y1 - y2) def a_star_search(graph, start, goal): frontier = PriorityQueue() frontier.put(start, 0) came_from = {} cost_so_far = {} came_from[start] = None cost_so_far[start] = 0 while not frontier.empty(): current = frontier.get() if current == goal: break for next in graph.neighbors(current): new_cost = cost_so_far[current] + graph.cost(current, next) if next not in cost_so_far or new_cost < cost_so_far[next]: cost_so_far[next] = new_cost #Notice!!Crucial difference! priority = new_cost + heuristic(goal, next) frontier.put(next, priority) came_from[next] = current return came_from, cost_so_far
这里给出的新的f(p)=cost_so_far(p)+heuristic(p)
其中启发式函数heuristic(p)在此处选用了曼哈顿距离作为启发函数
看起来非常有道理。
但是我当时对priority如此修改的正确性有怀疑,因为之前以cost_so_far划分优先级,是利用了”确定cost_so_far最小的点最短路一定已经确定“
现在的问题是,按照f(p)的优先级拓展,p点的最短路是否在拓展前已经确定?
回答这个问题的关键是意识到,"f(p)是递减的,且f(goal)的最终值就是最短路的长度”
其实我们不妨换一个想法,顺推有点想不通,尝试逆推证明其正确性
"当f(goal)都从堆里被取出来,说明其他点的历史f值或当前f值,因为f值在启发函数选用得当的情况下,总是大于实际最短路的长度的,也就是经过这些点的长度dis(goal)=f(goal)<dis(p)<f(p),这些p都没有可能产生新的使之更小的f(goal),而f(goal)就是我们最终想要的答案"
这让我想到卷福的一句话
“After eliminating all other possibilities, the one remaining-no matter how unlikely-must be the truth.”