Dijkstra大學的時候就學過,但最近在《Algorithms》這本書裏,看到了個頗有趣的講解,對這個算法有了新的理解,如下即是對這個算法的整理和實現。node
咱們知道,若是把全部的邊的距離當作1,廣度遍歷獲得的就是最短路徑。算法
更直觀的,咱們能夠這樣想,把節點都想成有質量的小球,節點間邊想成細線。咱們把S球拎起來,讓全部的球都天然下垂,這樣小球所在的層數,就是S球到其餘小球的最短距離了。以下圖:ruby
咱們能夠簡單證實下。優化
首先,廣度搜索,又被稱爲層次遍歷,就是一層一層進行搜索。當搜索到d層,全部小於d層的節點必定已經搜索完了。spa
假設在某一個時刻,全部d層的節點都已經計算正確了。那麼他們相鄰的節點的距離只有兩種狀況,一種是未知的,那麼他們同S的最短距離應爲d + 1。code
一種是已知的,距離小於等於d,根據假設,他的距離也已經計算正確了。blog
而後,咱們再考慮初始狀況。S節點的距離爲0,相鄰的節點距離都爲1,符合咱們的假設。因此經過廣度搜索獲得的距離是最短距離。隊列
這個"證實"只是用來理解這個算法的,並不去嚴謹,《算法導論》有嚴謹的證實,你們感興趣的話能夠去看看。圖片
既然咱們知道廣度搜索能獲得全部邊的距離爲1的節點的最短距離了。那麼廣度搜索和Dijkstra有沒有什麼關係呢?換句話說,最短路徑能不能變成廣度搜索呢?get
最短路徑和廣度搜索的區別就在於,廣度遍歷之間的節點距離只能爲1,而最短路徑計算的節點距離能夠爲任意正數。
若是咱們把全部距離都變成
1會怎樣?
爲了達到這個目的,咱們把兩個幾點之間加上若是啞節點(dummy node),讓全部節點(節點+啞幾點)間的距離爲1,這樣咱們就能夠用廣度遍從來算最短距離了!
好比s同u的距離爲3,那麼咱們能夠在S和U之間加上兩個節點u1 ,u2。這樣就變成了S -> U1 -> U2 -> U,相鄰的節點間的距離爲1。以下圖:
目前的算法看起來有點蠢,不過已經很接近Dijkstra了。咱們看下如何來優化它!
算法好玩的地方就在於,咱們能夠優化它。
首先,咱們要分析這個算法的問題。
假設從S到U的距離是10,那麼計算前99個啞節點的距離,都是沒有意義的。
若是咱們在90分鐘裏,睡一下,在第一百分鐘的時候,有一個鬧鈴把咱們叫醒,就行了!讓咱們來作這樣的一個鬧鈴!
假設咱們要求下圖的的最短距離,咱們能夠這麼作。咱們從0出發,從圖中能夠看出來,直到100或者200分鐘後,不會有任何有趣的事情發生。那咱們就設兩個鬧鈴,分別爲100分鐘、200分鐘以後響。
等到100這個鬧鈴響的時候,咱們到了1號節點,這個時候,咱們檢查1號節點的相鄰節點,發現50分鐘後,可能會到2號節點。因此咱們加入150這個鬧鈴。
150分鐘到的時候,咱們到了2號節點,因此2號的最短距離是150。這樣就獲得了全部節點的最短距離。
咱們的鬧鐘算法以下:
講S的鬧鐘設爲0
循環,直到沒有任何一個鬧鐘
假設,下一個鬧鐘(距離最近的節點)響起響起的時間是T,到達的節點是U,那麼
從S到U的距離就是T
對於U的每個相鄰節點V
若是v尚未鬧鐘,則將它的鬧鐘設置爲 T + l(u, v)
若是v的鬧鐘,比 T + l(u,v)晚,則更新v的鬧鐘
咱們來看下具體實現,實現算法能夠幫助咱們進一步理解算法。
class Dijkstra INFINITY = 1 << 32 def initialize(weighted_graph) @weighted_graph = weighted_graph end def shortest_distances make_alarms decrease_alarm(0, 0) while v = alarm! @weighted_graph[v].each do |w, weight| if alarm(w) > alarm(v) + weight decrease_alarm(w, alarm(v) + weight) end end end @alarms end def make_alarms @vertexes_alarms = (0...vertexes_count).to_a @alarms = Array.new(vertexes_count, INFINITY) end def alarm(vertex) @alarms[vertex] end def decrease_alarm(vertex, time) @alarms[vertex] = time @vertexes_alarms.sort_by! do |vertex| @alarms[vertex] end end def alarm! @vertexes_alarms.delete_at(0) end def vertexes_count @vertexes_count ||= @weighted_graph.length end end
本身觀察這個算法,會發現decrease_alarm
這個方法,每次都調用了sort!
方法。但其實咱們每次拿出來的鬧鈴都是數值最小的那一個,用優先隊列(Priority queue)更合適。這樣能夠下降算法的複雜度,讓咱們的算法更快。
算法的書有不少,但講清楚算法是怎麼來的,卻不多。《Algorithms》這本書採用的方式,很像《怎樣解題》所採用方式,頗有趣,也頗有啓發性。
同時算法最有趣的地方在於能夠一點一點改進和優化!