ACM算法--spfa算法--最短路算法
?求單源最短路的SPFA算法的全稱是:Shortest Path Faster Algorithm。?
????SPFA算法是西南交通大學段凡丁于1994年發表的。
????從名字我們就可以看出,這種算法在效率上一定有過人之處。?
????很多時候,給定的圖存在負權邊,這時類似Dijkstra等算法便沒有了用武之地,而Bellman-Ford算法的復雜度又過高,SPFA算法便派上用場了。有人稱spfa算法是最短路的萬能算法。
????簡潔起見,我們約定有向加權圖G不存在負權回路,即最短路徑一定存在。當然,我們可以在執行該算法前做一次拓撲排序,以判斷是否存在負權回路。
????我們用數組dis記錄每個結點的最短路徑估計值,可以用鄰接矩陣或鄰接表來存儲圖G,推薦使用鄰接表。
spfa的算法思想(動態逼近法):
????設立一個先進先出的隊列q用來保存待優化的結點,優化時每次取出隊首結點u,并且用u點當前的最短路徑估計值對離開u點所指向的結點v進行松弛操作,如果v點的最短路徑估計值有所調整,且v點不在當前的隊列中,就將v點放入隊尾。這樣不斷從隊列中取出結點來進行松弛操作,直至隊列空為止。?
????松弛操作的原理是著名的定理:“三角形兩邊之和大于第三邊”,在信息學中我們叫它三角不等式。所謂對結點i,j進行松弛,就是判定是否dis[j]>dis[i]+w[i,j],如果該式成立則將dis[j]減小到dis[i]+w[i,j],否則不動。?
????下面舉一個實例來說明SFFA算法是怎樣進行的:
和廣搜bfs的區別:
????SPFA 在形式上和廣度(寬度)優先搜索非常類似,不同的是bfs中一個點出了隊列就不可能重新進入隊列,但是SPFA中一個點可能在出隊列之后再次被放入隊列,也就是一個點改進過其它的點之后,過了一段時間可能本身被改進(重新入隊),于是再次用來改進其它的點,這樣反復迭代下去。
算法的描述:
最短路徑本身怎么輸出?
????在一個圖中,我們僅僅知道結點A到結點E的最短路徑長度,有時候意義不大。這個圖如果是地圖的模型的話,在算出最短路徑長度后,我們總要說明“怎么走”才算真正解決了問題。如何在計算過程中記錄下來最短路徑是怎么走的,并在最后將它輸出呢?
????我們定義一個path[]數組,path[i]表示源點s到i的最短路程中,結點i之前的結點的編號(父結點),我們在借助結點u對結點v松弛的同時,標記下path[v]=u,記錄的工作就完成了。
????如何輸出呢?我們記錄的是每個點前面的點是什么,輸出卻要從最前面到后面輸出,這很好辦,遞歸就可以了:?
【程序1】暢通工程 (laoj1138)
????????某省自從實行了很多年的暢通工程計劃后,終于修建了很多路。不過路多了也不好,每次要從一個城鎮到另一個城鎮時,都有許多種道路方案可以選擇,而某些方案要比另一些方案行走的距離要短很多。這讓行人很困擾。?
????現在,已知起點和終點,請你計算出要從起點到終點,最短需要行走多少距離。
輸入格式:
????第一行包含兩個正整數N和M(0<N<200,0<M<1000),分別代表現有城鎮的數目和已修建的道路的數目。城鎮分別以0~N-1編號。
????接下來是M行道路信息。每一行有三個整數A,B,X(0<=A,B<N,A!=B,0<X<10000),表示城鎮A和城鎮B之間有一條長度為X的雙向道路。?
????再接下一行有兩個整數S,T(0<=S,T<N),分別代表起點和終點。
輸出格式:
????輸出最短需要行走的距離。如果不存在從S到T的路線,就輸出-1。
樣例輸入1:
3 3
0 1 1
0 2 3
1 2 1
0 2
樣例輸入2:
3 1
0 1 1
1 2?
樣例輸出1:
2
樣例輸出2:
-1
【分析】 注意本題可能有結點為0的頂點,我就在這上面wa了很多次。并且有可能兩個城鎮之間有多條道路,我們要保留最小的那條。
?
?
spfa優化——深度優先搜索dfs
???????? 在上面的spfa標準算法中,每次更新(松弛)一個結點u時,如果該結點不在隊列中,那么直接入隊。
????但是有負環時,上述算法的時間復雜度退化為O(nm)。能不能改進呢?
????那我們試著使用深搜,核心思想為每次從更新一個結點u時,從該結點開始遞歸進行下一次迭代。
???????? 相比隊列,深度優先搜索有著先天優勢:在環上走一圈,回到已遍歷過的結點即有負環。絕大多數情況下的時間復雜度為O(m)級別。
????那我們試著使用深搜,核心思想為每次從更新一個結點u時,從該結點開始遞歸進行下一次迭代。
????對于WorldRings(ACM-ICPC Centrual European 2005)這道題,676個點,100000條邊,查找負環dfs僅僅需219ms。
????一個簡潔的數據結構和算法在一定程度上解決了大問題。
判斷存在負環的條件:重新經過某個在當前搜索棧中的結點。
【程序1】暢通工程 laoj1138 spfa算法(dfs): pascal code: var i,n,m,s,t,x,y,z:longint;a,b:array[0..201,0..201] of longint;q:array[0..10001] of integer;vis:array[0..201] of boolean;dis:array[0..201] of longint; procedure spfa(s:longint);var i:longint;beginfor i:=1 to b[s,0] doif dis[b[s,i]]>dis[s]+a[s,b[s,i]] thenbegindis[b[s,i]]:=dis[s]+a[s,b[s,i]];spfa(b[s,i]);end;end; beginread(n, m);fillchar(a,sizeof(a),0);for i:=1 to m dobeginreadln(x,y,z);if (a[x,y]<>0)and(z>a[x,y]) then continue;inc(b[x,0]);b[x,b[x,0]]:=y;a[x,y]:=z;inc(b[y,0]);b[y,b[y,0]]:=x;a[y,x]:=z;end;readln(s,t);for i:=0 to n do dis[i]:=99999999;dis[s]:=0;spfa(s);if dis[t]<>99999999 then writeln(dis[t]) else writeln(-1); end. C++ code: #include <iostream> using namespace std; int q[10001], dis[201], a[201][201], b[201][201]; bool vis[201]; int n, m, s, t; void spfa(int s){for(int i=1; i<=b[s][0]; i++)if (dis[b[s][i]] > dis[s]+a[s][b[s][i]]){dis[b[s][i]] = dis[s]+a[s][b[s][i]];spfa(b[s][i]);} } int main(){int x, y, z;cin >> n >> m; for(int i=0; i<m; i++){cin >> x >> y >> z; if (a[x][y]!=0 && z>a[x][y]) continue;b[x][0]++; b[x][b[x][0]]=y; a[x][y]=z; b[y][0]++; b[y][b[y][0]]=x; a[y][x]=z;}cin >> s >> t;for(int i=0; i<=n; i++) dis[i]=99999999;dis[s]=0;spfa(s);if (dis[t]!=99999999) cout << dis[t] << endl;else cout << -1 << endl;return 0; }
spfa優化——前向星優化
???????? 星形(star)表示法的思想與鄰接表表示法的思想有一定的相似之處。對每個結點,它也是記錄從該結點出發的所有弧,但它不是采用單向鏈表而是采用一個單一的數組表示。也就是說,在該數組中首先存放從結點1出發的所有弧,然后接著存放從節點2出發的所有孤,依此類推,最后存放從結點n出發的所有孤。對每條弧,要依次存放其起點、終點、權的數值等有關信息。這實際上相當于對所有弧給出了一個順序和編號,只是從同一結點出發的弧的順序可以任意排列。此外,為了能夠快速檢索從每個節點出發的所有弧,我們一般還用一個數組記錄每個結點出發的弧的起始地址(即弧的編號)。在這種表示法中,可以快速檢索從每個結點出發的所有弧,這種星形表示法稱為前向星形(forward star)表示法。
????例如,在下圖中,仍然假設弧(1,2),(l,3),(2,4),(3,2),(4,3),(4,5),(5,3)和(5,4)上的權分別為8,9,6,4,0,7,6和3。此時該網絡圖可以用前向星形表示法表示如下:
??
?
前向星存儲圖: #include <iostream> using namespace std; int first[10005]; struct edge{int point,next,len; } e[10005]; void add(int i, int u, int v, int w){e[i].point = v;e[i].next = first[u];e[i].len = w;first[u] = i; } int n,m; int main(){int u,v,w;cin >> n >> m;for (int i = 1; i <= m; i++){cin >> u >> v >> w;add(i,u,v,w);} //這段是讀入和加入for (int i = 0; i <= n; i++){cout << "from " << i << endl;for (int j = first[i]; j; j = e[j].next) //這就是遍歷邊了cout << "to " << e[j].point << " length= " << e[j].len << endl;} }總結
以上是生活随笔為你收集整理的ACM算法--spfa算法--最短路算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 620公里续航 大众ID. AERO纯电
- 下一篇: 超大杯命名揭晓!卢伟冰换上小米12S U