之前發現自己對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.”