圖論,最短路徑問題總結
維基百科定義最短路:
一個有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|那麼就可以說明存在負權環