《演算法圖解》總結第 7 章:狄克斯特拉演算法

yuxiaoyanran2020發表於2020-10-14

僅用於記錄學習,歡迎批評指正,大神勿噴

系列文章目錄

《演算法圖解》總結第 1 章:二分查詢、大O表示法; 《演算法圖解》總結第 2 章:陣列和連結串列,選擇排序; 《演算法圖解》總結第3章:while迴圈、遞迴、棧; 《演算法圖解》總結第4章:分而治之、快速排序; 《演算法圖解》總結第5章:雜湊表; 《演算法圖解》總結第6章:廣度優先搜尋

狄克斯特拉演算法

廣度優先搜尋找出的是非加權圖中段數最少的路徑,狄克斯特拉演算法找出的是加權圖中最快(短)的路徑。
狄克斯特拉演算法包含四個步驟:
(1)找出“最便宜”的節點,即可在最短時間內到達的節點;
(2)更新該節點的鄰居的開銷;
(3)重複這個過程,直到對圖中的每個節點都這樣做了;
(4)計算最終路徑。
狄克斯特拉演算法只適用於有向無環圖(DAG)。
如果有負權邊,就不能使用狄克斯特拉演算法,因為負權邊會導致這種演算法不管用,以下圖為例:
假設要用樂譜換架子鼓,因為5>0,所以第一步找出的“最便宜”的點是海報,更新其鄰居的開銷。
在這裡插入圖片描述
第二步找出“最便宜”的未處理節點:黑膠唱片
在這裡插入圖片描述
從此圖看出來海報節點也更新了開銷,但是第一步海報節點已經處理過了,這是一個危險訊號,因為節點一旦被處理,就意味著沒有前往該節點的更便宜的途徑,但是我們卻找到了前往海報更便宜的途徑。
架子鼓沒有任何鄰居,演算法結束,因此最終開銷如下:
在這裡插入圖片描述
由此可以得出樂譜換架子鼓的開銷為35美元,但是從圖中可以看出有一種交換方式為33美元,但是狄克斯特拉演算法沒有找到。為什麼呢?理由:狄克斯特拉演算法的假設是:對於處理過的海報節點,沒有前往該節點的更短路徑。這種假設僅在沒有負權邊時才成立。如果要找出在包含負權邊的圖中找出最短路徑,可使用另一種演算法:貝爾曼-福德演算法。

應用案例

案例:運用狄克斯特拉演算法找出下圖從起點到終點的最快路徑。
在這裡插入圖片描述

案例分析

要解決這個問題的程式碼,需要三個雜湊表:加權圖、開銷表、父節點表。

在這裡插入圖片描述

準備工作

一、表示加權圖:

graph = {}
graph["start"] = {}
graph["start"]["a"] = 6
graph["start"]["b"] = 2
graph["a"] = {}
graph["a"]["final"] = 1
graph["b"] = {}
graph["b"]["a"] = 3
graph["b"]["final"] = 5
graph["final"] = {}

二、 用雜湊表來儲存每個節點的開銷,即建立開銷圖

infinity = float("inf")  # 表示無窮大
costs = {}
costs["a"] = 6
costs["b"] = 2
costs["final"] = infinity # 到終點的開銷為無窮大

三、建立儲存父節點的雜湊表

parents = {}
parents["a"] = "start"
parents["b"] = "start"
parents["final"] = 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迴圈在所有節點都被處理過後結束
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("Cost from the start to each node:")
print(costs)

輸出結果:

Cost from start to each node:
{'a': 5, 'b': 2, 'final': 6}

相關文章