最短路徑 之 SPFA算法

轉載http://apps.hi.baidu.com/share/detail/17506254

 

       求最短路徑的算法有許多種,除了排序外,恐怕是OI界中解決同一類問題算法最多的了。最熟悉的無疑是Dijkstra,接着是Bellman-Ford,它們都可以求出由一個源點向其他各點的最短路徑;如果我們想要求出每一對頂點之間的最短路徑的話,還可以用Floyd-Warshall。

       SPFA是這篇日誌要寫的一種算法,它的性能非常好,代碼實現也並不複雜。特別是當圖的規模大,用鄰接矩陣存不下的時候,用SPFA則可以很方便地面對臨接表。每個人都寫過廣搜,SPFA的實現和廣搜非常相似。

       如何求得最短路徑的長度值?

       首先說明,SPFA是一種單源最短路徑算法,所以以下所說的“某點的最短路徑長度”,指的是“某點到源點的最短路徑長度”。

       我們記源點爲S,由源點到達點i的“當前最短路徑”爲D[i],開始時將所有D[i]初始化爲無窮大,D[S]則初始化爲0。算法所要做的,就是在運行過程中,不斷嘗試減小D[]數組的元素,最終將其中每一個元素減小到實際的最短路徑。

過程中,我們要維護一個隊列,開始時將源點置於隊首,然後反覆進行這樣的操作,直到隊列爲空:

(1)從隊首取出一個結點u,掃描所有由u結點可以一步到達的結點,具體的掃描過程,隨存儲方式的不同而不同;

(2)一旦發現有這樣一個結點,記爲v,滿足D[v] > D[u] + w(u, v),則將D[v]的值減小,減小到和D[u] + w(u, v)相等。          其中,w(u, v)爲圖中的邊u-v的長度,由於u-v必相鄰,所以這個長度一定已知(不然我們得到的也不叫一個完整的           圖);這種操作叫做鬆弛。

引用內容
鬆弛操作的原理是著名的定理:“三角形兩邊之和大於第三邊”,在信息學中我們叫它三角不等式。所謂對i,j進行

鬆弛,就是判定是否d[j]>d[i]+w[i,j],如果該式成立則將d[j]減小到d[i]+w[i,j],否則不動。

(3)上一步中,我們認爲我們“改進了”結點v的最短路徑,結點v的當前路徑長度D[v]相比於以前減小了一些,於是,與v相連的一些結點的路徑長度可能會相應地減小。注意,是可能,而不是一定。但即使如此,我們仍然要將v加入到隊列中等待處理,以保證這些結點的路徑值在算法結束時被降至最優。當然,如果連接至v的邊較多,算法運行中,結點v的路徑長度可能會多次被改進,如果我們因此而將v加入隊列多次,後續的工作無疑是冗餘的。這樣,就需要我們維護一個bool數組Inqueue[],來記錄每一個結點是否已經在隊列中。我們僅將尚未加入隊列的點加入隊列。


算法能否結束?

        對於不存在負權迴路的圖來說,上述算法是一定會結束的。因爲算法在反覆優化各個最短路徑長度,總有一個時刻會進入“無法再優化”的局面,此時一旦隊列讀空,算法就結束了。然而,如果圖中存在一條權值爲負的迴路,就糟糕了,算法會在其上反覆運行,通過“繞圈”來無休止地試圖減小某些相關點的最短路徑值。假如我們不能保證圖中沒有負權迴路,一種“結束條件”是必要的。這種結束條件是什麼呢?

        思考Bellman-Ford算法,它是如何結束的?顯然,最樸素的Bellman-Ford算法不管循環過程中發生了什麼,一概要循環|V|-1遍才肯結束。憑直覺我們可以感到,SPFA算法“更聰明一些”,就是說我們可以猜測,假如在SPFA中,一個點進入隊列——或者說一個點被處理——超過了|V|次,那麼就可以斷定圖中存在負權迴路了。


最短路徑本身怎麼輸出?

       在一幅圖中,我們僅僅知道結點A到結點E的最短路徑長度是73,有時候意義不大。這附圖如果是地圖的模型的話,在算出最短路徑長度後,我們總要說明“怎麼走”纔算真正解決了問題。如何在計算過程中記錄下來最短路徑是怎麼走的,並在最後將它輸出呢?

       Path[]數組,Path[i]表示從S到i的最短路徑中,結點i之前的結點的編號。注意,是“之前”,不是“之後”。最短路徑算法的核心思想成爲“鬆弛”,原理是三角形不等式,方法是上文已經提及的。我們只需要在藉助結點u對結點v進行鬆弛的同時,標記下Path[v] = u,記錄的工作就完成了。

輸出時可能會遇到一點難處,我們記的是每個點“前面的”點是什麼,輸出卻要從最前面往最後面輸,這不好辦。其實很好辦,見如下遞歸方法:

 

程序代碼

SPFA的代碼怎麼寫?

       我寫了鄰接表和鄰接矩陣兩種,兩者想像起來是那麼的不同,算法的思路上實在區別不大,只是用不同方式詮釋“掃描”的過程而已。只給出SPFA的單個函數,我不覺得很容易看懂,但是我仍然把兩個程序的SPFA函數放在下面。在日誌的結尾處,有一個完整版文件下載。貼程序,首先是鄰接表的:

 

程序代碼

 

然後是鄰接矩陣的: 

程序代碼

另:http://blog.csdn.net/bobcowwocb/archive/2009/09/14/4550188.aspx

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章