Dijkstra演算法和Floyd演算法對比分析
首先,Dijkstra演算法與Floyd演算法都是廣度優先搜尋的演算法。都可以用來求單源點到其他所有點的最短路徑。那麼這兩者的原理分別是怎樣?彼此又有什麼區別呢?
求此有向圖中起點1到其他所有點的最短距離
在本文中,我們以一個小小的包含3個節點的有向圖和鄰接矩陣Graph來進行說明。
Graph[3][3] = {0,5,6
1000,0,1000
1000,-2,0}
- 1
- 2
- 3
- 1
- 2
- 3
Dijskra演算法
採用Dijkstra演算法,需要有一個源點集合u,一個參考點集合v,以及一個滾動dist陣列,用於儲存S點到達其他所有點的最短路徑。
如果需要記錄最短路線,還需要配置一個pre陣列,用來儲存當前最短路徑下Index序號的點的前驅節點。
初始化時,u中只有源點s,其他點都在v中,dist陣列是s到其他所有點的直接距離,若沒有邊連線則為無窮大(本例中1000表示無窮大)。
初始化狀態:u={1},v={2,3},dist[]={0,5,6},pre[]={1,1,1},flag={0,1,1}
演算法開始,遍歷dist查詢最小值,將該最小值對應的點新增到u中,並從v中刪除。一種實現方式是採用flag陣列,0表示在u中,1表示在v中。
中間狀態1:u={1,2},v={3},dist[]={0,5,6},pre[]={1,1,1},flag={0,0,1}
接著需要更新dist陣列,判斷從起始點1到v中的點之間的路徑上插入新加入的點2,路徑是否能變得更短,也就是比較dist[j]和Graph[1][2]+dist[2],然後使用較小值更新dist[j],若dist[j]被更新,則將pre[j]修改為2.
中間狀態2:u={1,2},v={3},dist[]={0,5,6},pre[]={1,1,1},flag={0,0,1}
然後迴圈上述步驟,判斷在v中且dist最小的點,然後加入到u中。
中間狀態3: u={1,2,3},v={},dist[]={0,5,6},pre[]={1,1,1},flag={0,0,0}
此時發現v中已沒有點,則結果被輸出。
很顯然,這個結果是不對的,從1到2的最短路徑應該是1->3->2,長度為4. 而不是1->2,長度為5
這是因為按照Dijkstra的演算法邏輯,是不能計算負權圖的。
Dijkstra演算法本質上是貪心演算法,下一條路徑都是由當前更短的路徑派生出來的更長的路徑。不存在回溯的過程。
如果權值存在負數,那麼被派生出來的可能是更短的路徑,這就需要過程可以回溯,之前的路徑需要被更短的路徑替換掉,而Dijkstra演算法是不能回溯的。它每一步都是以當前最優選擇為前提的。
Floyd演算法
那麼,Floyd演算法會怎麼做呢?Floyd演算法實際上是一個動態規劃演算法。
每一個點對u和v之間的最短路徑,可能會經過N個點,這些中間點記為k。
假定u到k之間的最短路徑已經找好,k到v之間的最短路徑已經找好,那麼求u到v之間的最短路徑,就是遍歷各個可能的k點,然後求(u,k)+(k,v)之間的最小值。
所以這實際上將大規模的問題自頂向下劃分為了小規模的問題,這就是動態規劃思想。
那麼演算法的步驟是怎樣的呢?
需要使用三層迴圈:
for(u){
for(v){
for(k)}}
- 1
- 2
- 3
- 1
- 2
- 3
若graph[u][k]+graph[k][v] < graph[u][v],則更新graph[u][v]。並記錄以當前u為起點的情況下此點的前驅。
當三層遍歷完畢之後,所有點對之間的最短路徑長度和路徑就能求出來了。當然,如果只需要求某個點到其他所有點的最短距離,那麼固定u,也就是說只用兩層遍歷就可以做到了。
最後的矩陣為
{0,4,6
1000,0,1000
998,-2,0}
- 1
- 2
- 3
- 1
- 2
- 3
可以看到,求得的最短路徑及其長度均是正確的。
這是因為動態規劃是可以回溯的,會遍歷到從1到3再從3到2的路徑。
總結
上述演算法均為兩種經典演算法的最簡單形式,沒有任何優化。比如Floyd可以從空間複雜度上進行優化,Dijkstra在選擇v中dist最小值時可以使用堆排序等。
本文意在引出一個關於貪心演算法和動態規劃演算法之間區別與聯絡的論述。(出自鄒博老師)
考慮一階馬爾科夫模型,狀態N僅僅可以從狀態N-1得到,就像有限狀態自動機,這就是正確使用貪心演算法的前提。
考慮高階馬爾科夫模型,狀態N可能需要前面的狀態N-1,N-2,N-3等等一起聯合才能得到。這就是正確使用動態規劃的前提。
所以一定能用貪心演算法解的問題肯定可以由動態規劃解。但是可以用動態規劃來解的問題,不一定能用貪心演算法來解。
使用馬爾科夫模型來類比動態規劃思想的這個觀點還有很多啟發思維的地方。
比如說在構建狀態轉移方程時,經常因為使用的狀態不對,而列不出最終的狀態轉移方程。
簡單的狀態轉移方程,只需要考慮一個狀態x的變化,而複雜的狀態轉移方程可能需要考慮x、y或者更多的狀態遷移。那麼如何找準這些影響最終結果的狀態,並找準狀態和結果之間的對應關係,是列好狀態轉移方程的一個重點。
https://blog.csdn.net/qq_17368865/article/details/79062348