1. 程式人生 > >簡單易懂——Dijkstra演算法講解

簡單易懂——Dijkstra演算法講解

前言:



相對於暴力簡單的Floyd演算法,Dijkstra演算法更為有用且複雜度較為合理--O(N^2)。今天就為大家介紹一下這個演算法。Dijkstra演算法使用了廣度優先搜尋解決賦權有向圖或者無向圖單源最短路徑問題,演算法最終得到一個最短路徑樹。該演算法常用於路由演算法或者作為其他圖演算法的一個子模組。


演算法思路:

Dijkstra演算法採用的是一種貪心的策略,宣告一個數組dis來儲存源點到各個頂點的最短距離和一個儲存已經找到了最短路徑的頂點的集合:T,初始時,原點 s 的路徑權重被賦為 0 (dis[s] = 0)。若對於頂點 s 存在能直接到達的邊(s,m),則把dis[m]設為w(s, m),同時把所有其他(s不能直接到達的)頂點的路徑長度設為無窮大。初始時,集合T只有頂點s然後,從dis陣列選擇最小值,則該值就是源點s到該值對應的頂點的最短路徑,並且把該點加入到T中,此時完成一個頂點, 然後,我們需要看看新加入的頂點是否可以到達其他頂點並且看看通過該頂點到達其他點的路徑長度是否比源點直接到達短,如果是,那麼就替換這些頂點在dis中的值。 然後,又從dis中找出最小值,重複上述動作,直到T中包含了圖的所有頂點。

Dijkstra演算法

090644t797fce7n20of7j9.png與Floyd-Warshall演算法一樣這裡仍然使用二維陣列e來儲存頂點之間邊的關係,初始值如下。090651l6pt4666tptut66u.png我們還需要用一個一維陣列dis來儲存1號頂點到其餘各個頂點的初始路程,如下。090657ofidcactthcig33i.png我們將此時dis陣列中的值稱為最短路的“估計值”。

Step 1:

       既然是求1號頂點到其餘各個頂點的最短路程,那就先找一個離1號頂點最近的頂點。通過陣列dis可知當前離1號頂點最近是2號頂點。當選擇了2號頂點後,dis[2]的值就已經從“估計值”變為了“確定值”,即1號頂點到2號頂點的最短路程就是當前dis[2]值。為什麼呢?你想啊,目前離1號頂點最近的是2號頂點,並且這個圖所有的邊都是正數,那麼肯定不可能通過第三個頂點中轉,使得1號頂點到2號頂點的路程進一步縮短了。因為1號頂點到其它頂點的路程肯定沒有1號到2號頂點短

Step 2:

       既然選了2號頂點,接下來再來看2號頂點有哪些出邊呢。有2->3和2->4這兩條邊。先討論通過2->3這條邊能否讓1號頂點到3號頂點的路程變短。也就是說現在來比較dis[3]和dis[2]+e[2][3]的大小。其中dis[3]表示1號頂點到3號頂點的路程。dis[2]+e[2][3]中dis[2]表示1號頂點到2號頂點的路程,e[2][3]表示2->3這條邊。所以dis[2]+e[2][3]就表示從1號頂點先到2號頂點,再通過2->3這條邊,到達3號頂點的路程。

Step 3:

       我們發現dis[3]=12,dis[2]+e[2][3]=1+9=10,dis[3]>dis[2]+e[2][3],因此dis[3]要更新為10。這個過程有個專業術語叫做“鬆弛”。即1號頂點到3號頂點的路程即dis[3],通過2->3這條邊鬆弛成功。這便是Dijkstra演算法的主要思想:通過“邊”來鬆弛1號頂點到其餘各個頂點的路程。
       同理通過2->4(e[2][4]),可以將dis[4]的值從∞鬆弛為4(dis[4]初始為∞,dis[2]+e[2][4]=1+3=4,dis[4]>dis[2]+e[2][4],因此dis[4]要更新為4)。

Step 4:

       剛才我們對2號頂點所有的出邊進行了鬆弛。鬆弛完畢之後dis陣列為:090706vmjy7l2ee2lyalia.png       接下來,繼續在剩下的3、4、5和6號頂點中,選出離1號頂點最近的頂點。通過上面更新過dis陣列,當前離1號頂點最近是4號頂點。此時,dis[4]的值已經從“估計值”變為了“確定值”。下面繼續對4號頂點的所有出邊(4->3,4->5和4->6)用剛才的方法進行鬆弛。鬆弛完畢之後dis陣列為:090714f2p1wppynngj2pep.png       繼續在剩下的3、5和6號頂點中,選出離1號頂點最近的頂點,這次選擇3號頂點。此時,dis[3]的值已經從“估計值”變為了“確定值”。對3號頂點的所有出邊(3->5)進行鬆弛。鬆弛完畢之後dis陣列為:090722ywunackk35i8cni5.png       繼續在剩下的5和6號頂點中,選出離1號頂點最近的頂點,這次選擇5號頂點。此時,dis[5]的值已經從“估計值”變為了“確定值”。對5號頂點的所有出邊(5->4)進行鬆弛。鬆弛完畢之後dis陣列為:090730eq6oqzyq7laqha9y.png       最後對6號頂點所有點出邊進行鬆弛。因為這個例子中6號頂點沒有出邊,因此不用處理。到此,dis陣列中所有的值都已經從“估計值”變為了“確定值”。       最終dis陣列如下,這便是1號頂點到其餘各個頂點的最短路徑。090738azt5clcozl899ekt.png       現在來總結一下剛才的演算法。演算法的基本思想是:每次找到離源點(上面例子的源點就是1號頂點)最近的一個頂點,然後以該頂點為中心進行擴充套件,最終得到源點到其餘所有點的最短路徑。基本步驟如下:
  • 將所有的頂點分為兩部分:已知最短路程的頂點集合P和未知最短路徑的頂點集合Q。最開始,已知最短路徑的頂點集合P中只有源點一個頂點。我們這裡用一個book[ i ]陣列來記錄哪些點在集合P中。例如對於某個頂點i,如果book[ i ]為1則表示這個頂點在集合P中,如果book[ i ]為0則表示這個頂點在集合Q中。

  • 設定源點s到自己的最短路徑為0即dis=0。若存在源點有能直接到達的頂點i,則把dis[ i ]設為e[s][ i ]。同時把所有其它(源點不能直接到達的)頂點的最短路徑為設為∞。

  • 在集合Q的所有頂點中選擇一個離源點s最近的頂點u(即dis[u]最小)加入到集合P。並考察所有以點u為起點的邊,對每一條邊進行鬆弛操作。例如存在一條從u到v的邊,那麼可以通過將邊u->v新增到尾部來拓展一條從s到v的路徑,這條路徑的長度是dis[u]+e[u][v]。如果這個值比目前已知的dis[v]的值要小,我們可以用新值來替代當前dis[v]中的值。

  • 重複第3步,如果集合Q為空,演算法結束。最終dis陣列中的值就是源點到所有頂點的最短路徑。

參考文章:http://blog.51cto.com/ahalei/1387799