Dijkstra演算法

線上找猹發表於2020-10-23

本文圖片及演算法均來自 《演算法導論》,建議閱讀英文原版,裡面有更加詳細的介紹和演算法證明。

在進入Dijkstra演算法之前先把書中一些關於圖的表達表示清楚,會更加的清晰。因為本質上Dijkstra演算法是一個基於圖的演算法,如果不把一些圖的基本概念搞清楚可能會出現難於理解或者理解的偏差。所以這裡還是覺得,有能力一定要自己去看英文的原版,儘可能的減少翻譯或者別人解釋所帶來的的理解偏差。

圖G的基本表示,G = (V,E)。 V(vertex)為圖裡所有頂點,E(edge)為圖裡所有的邊,w(U,V)為從頂點U到頂點V的權重,可以簡單的理解為距離(如果是想找一條最短距離的話),如果要更詳細一些的話,一個帶權重的圖可以表示為G = (V,E,w),w為所有邊的權重的集合。

圖的表示有兩種,如下圖所示,這裡的所有演算法都是基於圖是用Adjacency list來表示的(下圖b中所示)。比如,G.adj [1] = {2,5}, 說明在圖G中,頂點1和頂點2,5相連。
圖的簡單表示

Dijkstra’s algorithm:
Given: A weighted, directed graph G(V,E) with edge weights w, w stores weight w(u,v) for each edge (u,v) belongs to E. Assume w(u,v) >= 0.
輸入:一個有向權重圖(無向圖也可以),並且各權重大於等於0。起點用source s表示。
返回:?

這裡返回的其實是兩個陣列,一個陣列表示起始點source s到各個頂點的最短距離,另一個陣列表示在最短路徑中,各頂點的前任頂點(predecessor)也可以理解為父頂點,比如我們有一條最短路徑從s到b,s -> a -> b, 那麼陣列裡b對應的父頂點就為a。
第一個陣列很好理解,我們要求最短的路徑嘛,從s到各個頂點的距離直接陣列裡取出來就行了。
第二個陣列是幹嘛用的呢?為什麼要記錄頂點的父頂點呢?很自然,我們不止想要知道最短路徑的距離,我們還想知道路徑是怎麼來的,這時候,我們只要從終點開始,順著終點的父頂點,一路回溯到起點就可以了。這裡也貼一個書裡關於列印路徑的演算法。
在這裡插入圖片描述
PRINT PATH的引數為圖G,起點s,終點v。π 代表的是父頂點,v.π 表示的是頂點v的父頂點,v.π = NIL 說明頂點v沒有父頂點。這裡的v.π 我們可以把它轉換到陣列裡,其實表達的意思是一致的,但是為了方便表述,接下來都用書中的方法表示。同理,s到v的最短距離可以表示為v.d。

下面總結一下符號表示,v.d 表示s到v的最短距離,v.π 表示v的父頂點。v.π = NIL表示v沒有父頂點。

現在直接進行一個Dijkstra的演算法。
在這裡插入圖片描述
下面進入程式碼解讀。
引數:圖G,各個邊的權重w,起點s。
第一行:初始化。對於所有的頂點v,我們先假設s到頂點的最短距離為∞,相當於最開始我們沒有找到路徑,∞就表示路徑不存在。然後所有頂點的父頂點設成NIL,沒有父頂點。最後s.d = 0,這也很好理解,從s到頂點s的最短距離為0,從自己到自己不需要距離。
在這裡插入圖片描述
第二行:初始化一個空集S。這個S是幹嘛用的呢?這個S是用來儲存那些已經確定最短路徑的頂點的。注意一點,所有的表示都是基於頂點,當我們想知道頂點的最短路徑,我們直接去訪問那個頂點的屬性(attribute),比如v.d,v.π。一開始沒有任何確定最短路徑的頂點,除了起始點,但我們先不急著把起始點放進去。
第三行:初始化一個集合Q,包括了圖裡所有的頂點G.V。這裡的Q是一種特殊的資料結構,最小優先佇列,鍵為各個頂點的最短距離,v.d,這樣每次彈出的都會是最短距離最小的頂點。
第四行到第八行:

  1. Q中彈出從s到頂點最短距離最小的頂點u
  2. 把u加入S
  3. 對每個和u相鄰的頂點,做一個“放鬆”的操作

我們發現我們一直在從Q中彈出頂點(簡稱Q彈),當頂點都彈完了,並且加入到S中時,整個演算法結束,我們就能得到最開始所說的兩個量,到此頂點的最短距離,以及此頂點的父頂點

“放鬆”是什麼操作?本質上來說就是找到了一條更加好的路徑,代替原來的路徑。
在這裡插入圖片描述
RELAX:引數為頂點u, v以及各邊權重。
如果v.d > u.d + w(u,v)? 這是什麼意思呢?v.d 我們知道是當前到v的最短距離,如果這個距離大於某個距離,說明它其實並不是最短距離,為什麼會這樣呢,式子裡u.d + w(u,v)是什麼意思呢?u.d是當前到u的最短距離,w(u,v)是邊 u->v 的距離,說明我們可以先到u,再到v,並且使這兩段的距離之和比原來到v的距離短。如果式子成立的話,那麼到v的最短距離v.d 就會變成u.d + w(u,v), 而v的父節點將變成u (因為路徑中是從u 到 v)。

**所有通俗上來說,Dijkstra的第7到8行表示的意思就是,我們想看一看有沒有可能通過u,從u到v,來使得它相鄰頂點v的最短距離變短,即v.d > u.d + w(u,v)。**其實這裡的相鄰頂點應該取不在S中的頂點,相當於未探索的頂點,而不是所有的相鄰頂點,因為有些相鄰頂點的可能已經在S中了,那就說明該頂點的最短路徑已經確定,不需要再進行操作了。更嚴謹一些應該是:
for each vertex v in G.adj [u] and v is not in S:
RELAX(u, v, w)
這樣可以避免一些多餘操作,當然你不加這個判定應該也沒事,因為該頂點已經到達最優了,也不會進行“放鬆”操作。

綜上所述,整個演算法都是基於頂點的性質來進行的,比如v.d, v.π,還有圖的表示是基於Adjacency list,這樣的好處就是足夠的抽象,足夠靈活,讓整個演算法的表示非常的簡潔明瞭。

相關文章