Dijkstra演算法及正確性分析

weiwenhao發表於2018-04-11

最近朋友問了一個關於列車排程的問題,求兩個地點之間的最短路徑。聽起來挺簡單的問題,可是仔細思考後發現完全無從下手。最近空閒下來便惡補了一番資料結構。

求最短路徑的方法有Dijkstra,Floyd,BFS等等 其中Floyd適合多源最短路徑,BFS適合無權值的情況,這裡問題屬於單源最短路徑,所以我們採用Dijkstra演算法.

開幹吧!拿出地鐵卡就是一頓畫,現在我們就把問題抽象為求寶安中心-老街的最短路徑

Dijkstra演算法及正確性分析

開始之前首先我們介紹幾個概念

found集合

對於這種起點到某個頂點的真正的最短路徑,我們稱它為全域性最短路徑。已經找到全域性最短路徑的頂點,我們將其儲在found集合中.現在來初始化一下found

   // 初始時,我們已知的就只有寶安中心到寶安中心的最短路徑
   $found = ['寶安中心'];
複製程式碼

dist集合

用來儲存起點(寶安中心)到某個頂點u, 相對於S的最短路徑。通俗解釋就是:寶安中心到世界之窗經過了新安,那麼必須有新安∈ S。 否則我們無法直接知道其相對最短路徑,在dist中則將其記為不可能存在的大值,用來表示這個點我們目前還沒有辦法探索到。

dist中儲存的最短路徑稱之為相對於S的最短路徑。下文我們簡稱為相對最短路徑,和其對應的我們有一個全域性最短路徑

初始一下dist集合

const MAX = 65525; //65525是我們定義的一個不可能出現的大值

 $dist = [
     '深圳北站' => 5,
     '新安' => 8,
     '世界之窗' => MAX,
     '福田' => MAX,
     '購物公園' => MAX,
     '老街' => MAX,
     '布吉' => MAX,
 ];    
複製程式碼

演算法描述

開始我們的演算法~ 我們首先找到dist中的最小值,這裡是 深圳北站 => 5

順便告訴你一個激動人心的訊息,我們找到了寶安中心到深圳北站的全域性最短路徑。

what?為什麼dist中的最小值就是我們要找的全域性最短路徑(這裡是寶安中心-深圳北站)? 我們要找的不是寶安中心-老街的全域性最短路徑嗎,知道了寶安中心-深圳北站的最短路徑有什麼用嗎? 我現在沒法給你一個很好的解釋,我們繼續往下看。

繼續進行演算法,下面是紅色頂點表示已經確定了全域性最短路徑的頂點

Dijkstra演算法及正確性分析
既然已經又找到了一個全域性最短路徑頂點,我們就把它更新到found集合中

$found = ['寶安中心', '深圳北站'];
複製程式碼

集合found中的元素增加了一枚後,我們的視野變的寬廣了。我們可以通過深圳北站作為一箇中轉更新 dist這個相對最短路徑集合了

Dijkstra演算法及正確性分析

通過深圳北站這個中轉站我們可以得到已下相對最短路徑

寶安中心-深圳北站-新安 = 5 + 2 = 7 寶安中心-深圳北站-福田 = 5 + 5 = 10 寶安中心-深圳北站-布吉 = 5 + 10 = 15

現在可以馬上去替換我們dist集合中的值了嗎?別急,我們需要的是相對最短路徑,可不是什麼阿貓阿狗就能進來的。所以我們需要進行一個比較.

// 如果新的相對最短路徑比原有的相對最短路徑要小,我們則進行一個更新
if ($newWeight < $dist['新安']) {
    $dist['新安'] = $newWeight;
}
複製程式碼

dist集合更新如下

$dist = [
     '深圳北站' => 5, // ok
     '新安' => 7, //8 -> 7
     '世界之窗' => MAX,
     '福田' => 10, // MAX -> 10
     '購物公園' => MAX,
     '老街' => MAX,
     '布吉' => 15 // MAX -> 15
 ];   
複製程式碼

現在我們重複之前的步驟,找一個最小值,其就是我們下一個全域性最短路徑。要記住,深圳北站就不要加入查詢佇列了,其已經被found了

人眼掃描後可以確定下一個全域性最短路徑的頂點為新安。並且有了新安的中轉,我們可以再次拓寬我們的視野

為了表示清晰,對於還沒有探索到相對最短路徑,先隱藏其權重值

Dijkstra演算法及正確性分析
更新後的founddist如下

$found = ['寶安中心', '深圳北站','新安'];

$dist = [
     '深圳北站' => 5, // ok
     '新安' => 7, // ok
     '世界之窗' => 16, // MAX -> 16 = 9+7 = 寶安->新安 + 新安->世界之窗
     '福田' => 10, // MAX -> 10
     '購物公園' => MAX,
     '老街' => MAX,
     '布吉' => 15
 ]; 
 
複製程式碼

再次迴圈 (目標已經出現在我們的視野中啦,彆著急,我們還沒有確定其全域性最短路徑) ↓

Dijkstra演算法及正確性分析

更新後的s和dist如下

$found = ['寶安中心', '深圳北站', '新安', '福田'];

$dist = [
     '深圳北站' => 5, // ok
     '新安' => 7, // ok
     '世界之窗' => 16,
     '福田' => 10, // ok
     '購物公園' => 12, // MAX -> 12
     '老街' => 15, // MAX -> 15
     '布吉' => 15
 ]; 
 
複製程式碼

再次迴圈↓

Dijkstra演算法及正確性分析

更新後的found和dist如下

$s = ['寶安中心', '深圳北站', '新安', '福田', '購物公園'];

$dist = [
     '深圳北站' => 5, // ok
     '新安' => 7, // ok
     '世界之窗' => 16,
     '福田' => 10, // ok
     '購物公園' => 12, // ok
     '老街' => 15,
     '布吉' => 15
 ]; 
 
複製程式碼

再次尋找dist中最小值時, 找到了我們的目標,老街。

Dijkstra演算法及正確性分析

演算法描述完畢!

演算法實現

github.com/weiwenhao/a…

正確性分析

為什麼dist集合中的最小值就是我們要找的全域性最短路徑?

$dist = [
     '福田' => 10, // ok
     '購物公園' => 12,
     '老街' => 15,
 ]; 
複製程式碼

以某一次dist集合的部分資料為例子,按照演算法描述 寶安中心-福田-購物公園是我們要找的全域性最短路徑。現在我們假設到寶安中心-購物公園存在更短的路徑,則存在如下兩種情況

情況1

Dijkstra演算法及正確性分析

紅色區域代表集合found,表示已經找到了全域性最短路徑的頂點集合。 上面提到過,dist集合中儲存的是相對於S的最短路徑。

對於這種情況,演算法在進行dist集合更新操作的時候就已經判斷了寶安中心-福田-購物公園寶安中心-X-購物公園之間的更小值,因此這種情況不可能存在。我們繼續來看另外一種更加可能出現的情況

  • 情況2

Dijkstra演算法及正確性分析

是否會存在這樣一條最短路徑呢?因為y-購物公園的距離我們並沒有探索過,所以這種情況是需要慎重思考一種情況。

先讓時光倒流

Dijkstra演算法及正確性分析

此時我們的dist集合中一共有5個頂點。其中寶安中心,福田,X已經被加入到了found集合中。Y通過X的中轉後被發現,購物公園通過福田中專後被發現。 此時根據我們的演算法,將會在Y和購物公園中選取一個最小值,作為下一個全域性最短路徑頂點。這裡演算法選擇了購物公園。說明寶安中心-福田-購物公園 < 寶安中心-X-Y

回到情況2

Dijkstra演算法及正確性分析

在有了 寶安中心-福田-購物公園 < 寶安中心-X-Y前提下。 寶安中心-X-Y-購物公園 < 寶安中心-福田-購物公園是否能夠成立呢?假如等式不成立,則說明不可能存在一條比寶安中心-福田-購物公園更短的全域性最短路徑。

等式是否成立我相信你一目瞭然。

正確性分析完畢!

結語

你可能還在驚訝於dijkstra演算法為什麼這麼神奇?就算我們已經知道了演算法步驟,分析了演算法的正確性。可還是不禁會感嘆,到底是怎麼做到的,到底是怎麼找到最優解的?

回過頭去看看演算法描述你會發現,其實dijkstra並不知道自己什麼時候能夠找到自己想要的目標,它只是關注於眼前的最優解,然後碰巧在某一時刻眼前的最優解就是要尋找的目標值。這看起來有點笨,但是在某些情況十分有用,比如路由定址中查詢最短路徑必須要用到這種策略。

哦~對了,這種只關注於眼前最優解的方法其實有個更加有逼格的名字 —— 貪心演算法。

相關文章