1. 程式人生 > >圖論,最短路徑問題總結

圖論,最短路徑問題總結

維基百科定義最短路:

一個有6個節點和7條邊的圖

最短路徑問題圖論研究中的一個經典演算法問題, 旨在尋找圖(由結點和路徑組成的)中兩結點之間的最短路徑。 演算法具體的形式包括:

  • 確定起點的最短路徑問題 - 即已知起始結點,求最短路徑的問題。適合使用Dijkstra演算法
  • 確定終點的最短路徑問題 - 與確定起點的問題相反,該問題是已知終結結點,求最短路徑的問題。在無向圖中該問題與確定起點的問題完全等同,在有向圖中該問題等同於把所有路徑方向反轉的確定起點的問題。
  • 確定起點終點的最短路徑問題 - 即已知起點和終點,求兩結點之間的最短路徑。
  • 全域性最短路徑問題 - 求圖中所有的最短路徑。適合使用Floyd-Warshall演算法

用於解決最短路徑問題的演算法被稱做“最短路徑演算法”, 有時被簡稱作“路徑演算法”。 最常用的路徑演算法有:


1.floyd演算法  (弗洛伊德)(n^3複雜度)

基本思想:開始設集合S的初始狀態為空,然後依次將0,1,。。n-1定點加入,同時用d[i][j]儲存從i到j,僅經過S中的定點的最短路徑,在初始時刻,d[i][j] = A[i][j]中間不經過任何節點,然後依次向S中插入節點,並進行如下更新
d(k)[i][j] = min{  d(k-1)[i][j] ,  d(k-1)[i][k]+d(k-1)[k][j]  }
還可以使用一個二維陣列path指示最短路徑。
path[i][j]給出從定點i到j的最短路徑上,定點i的前一個頂點


程式碼相當簡單,最容易的實現方法:

for (k = 0;k < n;k++)
for (i = 0;i < n;i++)
for (j = 0;j < n;j++)
{
	if (d[i][k] + d[k][j] < d[i][j])
	{
		d[i][j] = d[i][k] + d[k][j];
		path[i][j] = path[k][j];
	}
}

可以通過遞推得出路徑的。。

2.dijstra (迪傑斯特拉演算法)演算法

單源最短路問題,先加入源,維持一張表來儲存此時到源中的最短距離,選取最小的加入,然後更新表,不斷的加入直到目的地在源中。僅適用於正邊權的時侯,因為這時我們可以保證任意加入的點已經找到了源到該點的距離。

3.bellman-ford演算法

最優性原理

它是最優性原理的直接應用,演算法基於以下事實:

如果最短路存在,則每個頂點最多經過一次,因此不超過n-1條邊;

長度為k的路由長度為k-1的路加一條邊得到;

由最優性原理,只需依次考慮長度為1,2,…,k-1的最短路。

適用條件&範圍

單源最短路徑(從源點s到其它所有頂點v);

有向圖&無向圖(無向圖可以看作(u,v),(v,u)同屬於邊集E的有向圖);

邊權可正可負(如有負權迴路輸出錯誤提示);

 差分約束系統(需要首先構造約束圖,構造不等式時>=表示求最小值,作為最長路,<=表示求最大值,作為最短路。<=構圖時,有負環說明無解;求不出最短路(為Inf)為任意解。>=構圖時類似)。  

演算法描述

 1)對每條邊進行|V|-1次Relax操作;

 2)如果存在(u,v)∈E使得dis[u]+w<dis[v],則存在負權迴路;否則dis[v]即為s到v的最短距離,pre[v]為前驅。  

for i:=1 to |V|-1 do //進行|v|-1次鬆弛得最短距離
    for 每條邊(u,v)∈E do   
        Relax(u,v,w);
for每條邊(u,v)∈E do //判斷是否存在負權環
    if dis[u]+w<dis[v] 
        Then Exit(False)

演算法時間複雜度O(VE)。因為演算法簡單,適用範圍又廣,雖然複雜度稍高,仍不失為一個很實用的演算法。  

改進和優化  如果迴圈n-1次以前已經發現不存在緊邊則可以立即終止;

4.spfa演算法

SPFA(Shortest Path Faster Algorithm)是Bellman-Ford演算法的一種佇列實現,減少了不必要的冗餘計算。它可以在O(kE)的時間複雜度內求出源點到其他所有點的最短路徑,可以處理負邊。

演算法流程  

SPFA對Bellman-Ford演算法優化的關鍵之處在於意識到:只有那些在前一遍鬆弛中改變了距離估計值的點,才可能引起他們的鄰接點的距離估計值的改變。因此,演算法大致流程是用一個佇列來進行維護,即用一個先進先出的佇列來存放被成功鬆弛的頂點。初始時,源點s入隊。當佇列不為空時,取出隊首頂點,對它的鄰接點進行鬆弛。如果某個鄰接點鬆弛成功,且該鄰接點不在佇列中,則將其入隊。經過有限次的鬆弛操作後,佇列將為空,演算法結束。SPFA演算法的實現,需要用到一個先進先出的佇列 queue 和一個指示頂點是否在佇列中的標記陣列mark。為了方便查詢某個頂點的鄰接點,圖採用臨界表儲存。

Procedure SPFA;
 
Begin
   initialize-single-source(G,s);
   initialize-queue(Q);
   enqueue(Q,s);
   while not empty(Q) do begin
      u:=dequeue(Q);
      for each v∈adj[u] do begin
         tmp:=d[v];
         relax(u,v);
         if (tmp<>d[v]) and (not v in Q) then enqueue(Q,v);
         end;
      end;
End;

注意:spfa演算法只有在不存在負權環的情況下可以正常的結束,如果存在負權環,那麼將總有頂點在入隊和出隊往返,佇列無法為空,這種情況下SPFA無法正常結束。可以通過新增一個變量表示每個頂點進入佇列的次數,如果大於|v|那麼就可以說明存在負權環