A*算法在求最短路上的應用


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM