《算法圖解》——第七章 狄克斯特拉算法


       第七章    狄克斯特拉算法

1  使用狄克斯特拉算法(Dijkstra’s algorithm)

用下圖舉個🌰:

該算法的四個步驟:

①找出"最便宜的節點",即可在最短時間內到達的節點,先找出

②更新該節點的鄰居的開銷

③重復這個過程,直到對圖中的每個節點都這樣做了

④計算最終路徑

第一步:找出最便宜的節點,假設需要時間無窮大,節點B是最近的——2分鍾。

第二步:計算經節點B前往其各個鄰居所需要的時間。

找到一條前往節點A的更短路徑。

對於節點B的鄰居,如果找到前往它的更短路徑,就更新其開銷。在這里,你找到了:

前往節點A的更短路徑(時間從6分鍾縮短到5分鍾);前往終點的更短路徑(時間從無窮大縮短到7分鍾)。

第三步:重復!

重復第一步:找出可在最短時間內前往的節點。你對節點B執行了第二步,除節點B外,可在最短時間內前往的節點是節點A。

重復第二步:更新節點A的所有鄰居的開銷。

你發現前往終點的時間為6分鍾!
你對每個節點都運行了狄克斯特拉算法(無需對終點這樣做)。現在,你知道:前往節點B需要2分鍾;前往節點A需要5分鍾;前往終點需要6分鍾。

廣度優先搜索來查找兩點之間的最短路徑,那時“最短路徑”的意思是段數最少。在狄克斯特拉算法中,你給每段都分配了一個數字或權重,因此狄克斯特拉算法找出的是總權重最小的路徑。

比較圖:


 

 

2  術語

該算法用於每條邊都有關聯數字的圖,這些數字稱為權重(weight)

帶權重的圖為加權圖(weighted graph),不帶權重的圖為非加權圖(unweighted graph)

計算非加權圖中的最短路徑,可使用廣度優先搜索。計算加權圖中的最短路徑,可使用狄克斯特拉算法。

可能會有環的存在:

繞環的路徑不可能是最短的路徑,在無向圖中,每條邊都是一個環。狄克斯特拉算法只適合於有向無環圖(directed acyclic graph,DAG)


 

 

3  換鋼琴

舉個🌰:

首先,創建一個表(和之前一樣無窮大):

第一步:找出最便宜的節點。換海報

第二步:計算前往該節點的各個鄰居的開銷。

再次執行第一步:下一個最便宜的節點是黑膠唱片——加5美元。你更新了架子鼓和吉他的開銷!這意味着經“黑膠唱片”前往“架子鼓”和“吉他”的開銷更低,因此你將這些樂器的父節點改為黑膠唱片。

再次執行第二步:更新黑膠唱片的各個鄰居的開銷。

下一個最便宜的是吉他,因此更新其鄰居的開銷。

你終於計算出了用吉他換鋼琴的開銷,於是你將其父節點設置為吉他。最后,對最后一個節點——架子鼓,做同樣的處理。

我們知道了最短路徑開銷是35,如何確定路徑呢?

先找出鋼琴的父節點:架子鼓。架子鼓的父節點:黑膠唱片。黑膠唱片的父節點:樂譜。得到完整的路徑。

最短路徑指的並不一定是物理距離,也可能是讓某種度量指標最小。


 

 

4  負權邊

舉個🌰:

第二種方式的開銷少2美元,應采取這種方式。但是,如果有負權變,就不能使用狄克斯特拉算法。

因為該算法假設:

對於處理過的海報節點,沒有前往該節點的更短路徑。這種假設僅在沒有負權邊時才成立。因此,不能將狄克斯特拉算法用於包含負權邊的圖。在包含負權邊的圖中,要找出最短路徑,可使用另一種算法——貝爾曼-福德算法(Bellman-Fordalgorithm)。


 

 

5  實現

 

 以下圖為🌰,用代碼實現狄克斯特拉算法,

首先,需要三個散列表

第一個散列表是整個圖的散列表,第二個散列表是開銷,第三個散列表是父子節點

還需要一個數組,用於記錄處理過的節點,因為對於同一個節點,你不用處理多次。processed = [ ]

算法如下:

代碼如下:(書中有詳細的每一步過程描述)

graph = {}            #整個圖的散列表
graph["start"] = {}
graph["start"]["a"] = 6
graph["start"]["b"] = 2
graph["a"] = {}
graph["a"]["fin"] = 1
graph["b"] = {}
graph["b"]["a"] = 3
graph["b"]["fin"] = 5
graph["fin"] = {}

infinity = float("inf")      #散列表是開銷
costs = {}
costs["a"] = 6
costs["b"] = 2
costs["fin"] = infinity

parents = {}            #散列表是父子節點
parents["a"] = "start"
parents["b"] = "start"
parents["fin"] = None

processed = []

def find_lowest_cost_node(costs):         
    lowest_cost = float("inf")        
    lowest_cost_node = None
    for node in costs:
        cost = costs[node]
        if cost < lowest_cost and node not in processed:
            lowest_cost = cost
            lowest_cost_node = node
    return lowest_cost_node

node = find_lowest_cost_node(costs)      #在未處理的節點中找出開銷最小的節點
while node is not None:             #在所有節點都被處理后結束
    cost = costs[node]
    neighbors = graph[node]
    for n in neighbors.keys():          #遍歷當前節點的所有鄰居
        new_cost = cost + neighbors[n]
        if costs[n] > new_cost:         #如果經當前節點前往該鄰居更近
            costs[n] = new_cost          #更新該鄰居的開銷
            parents[n] = node          #同時將該鄰居的父節點設置為當前節點
    processed.append(node)            #將當前節點標記為處理過
    node = find_lowest_cost_node(costs)    #找出接下來要處理的節點,並循環
print(costs)
print(parents)

 

練習
7.1 在下面的各個圖中,從起點到終點的最短路徑的總權重分別是多少?

 

A:8  B:60  C:負權邊,無法用狄克斯特拉算法無法找出最短路徑。


 

 

6  小結

廣度優先搜索用於在非加權圖中查找最短路徑。
狄克斯特拉算法用於在加權圖中查找最短路徑。
僅當權重為正時狄克斯特拉算法才管用。
如果圖中包含負權邊,請使用貝爾曼-福德算法。

 


免責聲明!

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



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