1. 程式人生 > >深入理解Dijkstra(迪傑斯特拉)演算法(正確思路+優化+原理)

深入理解Dijkstra(迪傑斯特拉)演算法(正確思路+優化+原理)

這裡需要一個很好的例子,這裡先拿網上流傳的例子作為反例。


問題1:不符合勾股定理

AC=3,CB=2,AB=6

難道這樣真的無傷大雅嗎?假設你的起點是A,終點是B,難道不應該是兩點之間線段最短嗎?、

問題2:完美地掃到了每個點

按照他的思維邏輯來,確實每個點都可以掃到,但是事實上,我們應該充分考慮沒掃到的情況之後是怎樣的,這樣才能真正理解Dijkstra演算法。


這個圖會很容易發現,按照Dijkstra的演算法,當前點走動的方向是如圖所示的。但是走到E點會發現此路不通,因為沒被訪問的點剩下了F,G,但是他們之間又是無窮遠的。如果讀者是這樣想的,那就進入思維誤區了。

正確思考:(設U是未走到過的集合,初始U={A,B,C,D,E,F,G})

第一次,BCDEFG都是無窮遠,設為1000好了,發現A和F、B是相連的,所以用A去更新F、B。


第二次,到達B,U中去除B,剩下CDEFG。到了B,發現DC是可達的,更新DC。發現D更短,去往D。


第三次,到達D,把D從U中去除,剩下CEFG。與D相連的有EC(另外的已訪問過所以不再考慮),這裡得出E為10,小於1000(無窮遠),可以被更新。但是看向C點,卻發現9>7.5更新不了。那麼A到C的最短路徑依然是ABC而不是ABDC。


到達E,這個時候U中剩下F,G。發現7.5+4大於10,還是更新不了,所以A到E還是ABDE最短。

這裡就回到了我們之前的關鍵點了。走Dijkstra演算法,不是靠走的,而是靠從U集合中取的!

所以這裡取出的是F,成功使用F更新了G,G被更新為7,FD 6+5>7更新不了。

其實A你也可以認為是0。

此外,剛才選取的時候,我們怎麼知道應該選取F而不是G?假設我們選的是G,但是他自己沒有被更新過,所以他也不能更新另外的值。所以我們需要加一個判斷,當前值是被更新過的才能去更新另外的值。換個角度,不加判斷其實也行。因為如果你取到了一個沒有被更新過的數,那麼就是1000,1000去更新另外的,只有可能比1000要大,所以他更新誰都不會成功,如果你加個判斷,等於是進行了一點點的優化。

梳理下流程,不斷從U中去取,然後不斷更新,直到U中被取完,這樣我們就得出了起點到達每個點的最短距離。

但是最短路徑怎麼求呢?之前我們到達E點斷掉了,說明靠走肯定不行,說明直接求最短路徑也肯定求不出來。所以我們和之前一樣,我們怎麼把最短路徑長度維護給每個點的,就怎麼把最短路徑交給每個點去維護。我們需要開一個二維陣列,以更新路徑的形式即可。

優化:採用優先佇列去優化。意思就是當你在A的時候,發現AB=3.5,AF=6,另外的1000,都更新他們,再把他們都丟進優先佇列,而優先佇列會自動替你把這一串,由小到大排好序。所以你只需要取佇列的隊首元素就可以了,省去了你找到最小值的過程。我們使用優先佇列就是節省了排序的時間。此外你可以用Vector或者鄰接表,不用像二維陣列一樣開一個矩陣,會更快。

原理:也就是為什麼我們要選取U中的最小值來更新而不是任意一個大小的值?因為只有這樣,你才能保證你每個點都能被更新成最小值。假設之前你用一個較大的值而沒有用最小值去更新,那麼就之後就可能會出現,你這個較大的值後來被更新了,但是一開始被你這個較大的值所更新的值並沒有被更新到。具體舉個例子,看最後一幅圖,假如我們一開始取的是F點,那麼我們就會把G點更新成7,那麼假如之後,發現一條更短的路徑到F,我們F點會被更新,但是G點卻不會被更新了啊,所以資料就失真了。那麼為什麼我們從最小的點開始取不會出現這個情況呢?舉個例子,我們根據選取最小值來選,AB=3.5,AF=6。那麼自然是選AB。然後由B去更新後面的值。這裡我們思考一下上面的那個問題,有沒有可能發現一條到達B更短的路,從而使得被B更新的最小值全部失效了?肯定就沒有了。那為什麼上面那個情況有?其實上面那個例子舉的不太好,不過講到這裡大家應該也都能明白是為什麼了。