ACM算法--spfa算法--最短路算法
?求單源最短路的SPFA算法的全稱(chēng)是:Shortest Path Faster Algorithm。?
????SPFA算法是西南交通大學(xué)段凡丁于1994年發(fā)表的。
????從名字我們就可以看出,這種算法在效率上一定有過(guò)人之處。?
????很多時(shí)候,給定的圖存在負(fù)權(quán)邊,這時(shí)類(lèi)似Dijkstra等算法便沒(méi)有了用武之地,而B(niǎo)ellman-Ford算法的復(fù)雜度又過(guò)高,SPFA算法便派上用場(chǎng)了。有人稱(chēng)spfa算法是最短路的萬(wàn)能算法。
????簡(jiǎn)潔起見(jiàn),我們約定有向加權(quán)圖G不存在負(fù)權(quán)回路,即最短路徑一定存在。當(dāng)然,我們可以在執(zhí)行該算法前做一次拓?fù)渑判?#xff0c;以判斷是否存在負(fù)權(quán)回路。
????我們用數(shù)組dis記錄每個(gè)結(jié)點(diǎn)的最短路徑估計(jì)值,可以用鄰接矩陣或鄰接表來(lái)存儲(chǔ)圖G,推薦使用鄰接表。
spfa的算法思想(動(dòng)態(tài)逼近法):
????設(shè)立一個(gè)先進(jìn)先出的隊(duì)列q用來(lái)保存待優(yōu)化的結(jié)點(diǎn),優(yōu)化時(shí)每次取出隊(duì)首結(jié)點(diǎn)u,并且用u點(diǎn)當(dāng)前的最短路徑估計(jì)值對(duì)離開(kāi)u點(diǎn)所指向的結(jié)點(diǎn)v進(jìn)行松弛操作,如果v點(diǎn)的最短路徑估計(jì)值有所調(diào)整,且v點(diǎn)不在當(dāng)前的隊(duì)列中,就將v點(diǎn)放入隊(duì)尾。這樣不斷從隊(duì)列中取出結(jié)點(diǎn)來(lái)進(jìn)行松弛操作,直至隊(duì)列空為止。?
????松弛操作的原理是著名的定理:“三角形兩邊之和大于第三邊”,在信息學(xué)中我們叫它三角不等式。所謂對(duì)結(jié)點(diǎn)i,j進(jìn)行松弛,就是判定是否dis[j]>dis[i]+w[i,j],如果該式成立則將dis[j]減小到dis[i]+w[i,j],否則不動(dòng)。?
????下面舉一個(gè)實(shí)例來(lái)說(shuō)明SFFA算法是怎樣進(jìn)行的:
和廣搜bfs的區(qū)別:
????SPFA 在形式上和廣度(寬度)優(yōu)先搜索非常類(lèi)似,不同的是bfs中一個(gè)點(diǎn)出了隊(duì)列就不可能重新進(jìn)入隊(duì)列,但是SPFA中一個(gè)點(diǎn)可能在出隊(duì)列之后再次被放入隊(duì)列,也就是一個(gè)點(diǎn)改進(jìn)過(guò)其它的點(diǎn)之后,過(guò)了一段時(shí)間可能本身被改進(jìn)(重新入隊(duì)),于是再次用來(lái)改進(jìn)其它的點(diǎn),這樣反復(fù)迭代下去。
算法的描述:
最短路徑本身怎么輸出?
????在一個(gè)圖中,我們僅僅知道結(jié)點(diǎn)A到結(jié)點(diǎn)E的最短路徑長(zhǎng)度,有時(shí)候意義不大。這個(gè)圖如果是地圖的模型的話,在算出最短路徑長(zhǎng)度后,我們總要說(shuō)明“怎么走”才算真正解決了問(wèn)題。如何在計(jì)算過(guò)程中記錄下來(lái)最短路徑是怎么走的,并在最后將它輸出呢?
????我們定義一個(gè)path[]數(shù)組,path[i]表示源點(diǎn)s到i的最短路程中,結(jié)點(diǎn)i之前的結(jié)點(diǎn)的編號(hào)(父結(jié)點(diǎn)),我們?cè)诮柚Y(jié)點(diǎn)u對(duì)結(jié)點(diǎn)v松弛的同時(shí),標(biāo)記下path[v]=u,記錄的工作就完成了。
????如何輸出呢?我們記錄的是每個(gè)點(diǎn)前面的點(diǎn)是什么,輸出卻要從最前面到后面輸出,這很好辦,遞歸就可以了:?
【程序1】暢通工程 (laoj1138)
????????某省自從實(shí)行了很多年的暢通工程計(jì)劃后,終于修建了很多路。不過(guò)路多了也不好,每次要從一個(gè)城鎮(zhèn)到另一個(gè)城鎮(zhèn)時(shí),都有許多種道路方案可以選擇,而某些方案要比另一些方案行走的距離要短很多。這讓行人很困擾。?
????現(xiàn)在,已知起點(diǎn)和終點(diǎn),請(qǐng)你計(jì)算出要從起點(diǎn)到終點(diǎn),最短需要行走多少距離。
輸入格式:
????第一行包含兩個(gè)正整數(shù)N和M(0<N<200,0<M<1000),分別代表現(xiàn)有城鎮(zhèn)的數(shù)目和已修建的道路的數(shù)目。城鎮(zhèn)分別以0~N-1編號(hào)。
????接下來(lái)是M行道路信息。每一行有三個(gè)整數(shù)A,B,X(0<=A,B<N,A!=B,0<X<10000),表示城鎮(zhèn)A和城鎮(zhèn)B之間有一條長(zhǎng)度為X的雙向道路。?
????再接下一行有兩個(gè)整數(shù)S,T(0<=S,T<N),分別代表起點(diǎn)和終點(diǎ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
【分析】 注意本題可能有結(jié)點(diǎn)為0的頂點(diǎn),我就在這上面wa了很多次。并且有可能兩個(gè)城鎮(zhèn)之間有多條道路,我們要保留最小的那條。
?
?
spfa優(yōu)化——深度優(yōu)先搜索dfs
???????? 在上面的spfa標(biāo)準(zhǔn)算法中,每次更新(松弛)一個(gè)結(jié)點(diǎn)u時(shí),如果該結(jié)點(diǎn)不在隊(duì)列中,那么直接入隊(duì)。
????但是有負(fù)環(huán)時(shí),上述算法的時(shí)間復(fù)雜度退化為O(nm)。能不能改進(jìn)呢?
????那我們?cè)囍褂蒙钏?#xff0c;核心思想為每次從更新一個(gè)結(jié)點(diǎn)u時(shí),從該結(jié)點(diǎn)開(kāi)始遞歸進(jìn)行下一次迭代。
???????? 相比隊(duì)列,深度優(yōu)先搜索有著先天優(yōu)勢(shì):在環(huán)上走一圈,回到已遍歷過(guò)的結(jié)點(diǎn)即有負(fù)環(huán)。絕大多數(shù)情況下的時(shí)間復(fù)雜度為O(m)級(jí)別。
????那我們?cè)囍褂蒙钏?#xff0c;核心思想為每次從更新一個(gè)結(jié)點(diǎn)u時(shí),從該結(jié)點(diǎn)開(kāi)始遞歸進(jìn)行下一次迭代。
????對(duì)于WorldRings(ACM-ICPC Centrual European 2005)這道題,676個(gè)點(diǎn),100000條邊,查找負(fù)環(huán)dfs僅僅需219ms。
????一個(gè)簡(jiǎn)潔的數(shù)據(jù)結(jié)構(gòu)和算法在一定程度上解決了大問(wèn)題。
判斷存在負(fù)環(huán)的條件:重新經(jīng)過(guò)某個(gè)在當(dāng)前搜索棧中的結(jié)點(diǎn)。
【程序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優(yōu)化——前向星優(yōu)化
???????? 星形(star)表示法的思想與鄰接表表示法的思想有一定的相似之處。對(duì)每個(gè)結(jié)點(diǎn),它也是記錄從該結(jié)點(diǎn)出發(fā)的所有弧,但它不是采用單向鏈表而是采用一個(gè)單一的數(shù)組表示。也就是說(shuō),在該數(shù)組中首先存放從結(jié)點(diǎn)1出發(fā)的所有弧,然后接著存放從節(jié)點(diǎn)2出發(fā)的所有孤,依此類(lèi)推,最后存放從結(jié)點(diǎn)n出發(fā)的所有孤。對(duì)每條弧,要依次存放其起點(diǎn)、終點(diǎn)、權(quán)的數(shù)值等有關(guān)信息。這實(shí)際上相當(dāng)于對(duì)所有弧給出了一個(gè)順序和編號(hào),只是從同一結(jié)點(diǎn)出發(fā)的弧的順序可以任意排列。此外,為了能夠快速檢索從每個(gè)節(jié)點(diǎn)出發(fā)的所有弧,我們一般還用一個(gè)數(shù)組記錄每個(gè)結(jié)點(diǎn)出發(fā)的弧的起始地址(即弧的編號(hào))。在這種表示法中,可以快速檢索從每個(gè)結(jié)點(diǎn)出發(fā)的所有弧,這種星形表示法稱(chēng)為前向星形(forward star)表示法。
????例如,在下圖中,仍然假設(shè)弧(1,2),(l,3),(2,4),(3,2),(4,3),(4,5),(5,3)和(5,4)上的權(quán)分別為8,9,6,4,0,7,6和3。此時(shí)該網(wǎng)絡(luò)圖可以用前向星形表示法表示如下:
??
?
前向星存儲(chǔ)圖: #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;} }總結(jié)
以上是生活随笔為你收集整理的ACM算法--spfa算法--最短路算法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 620公里续航 大众ID. AERO纯电
- 下一篇: 【牛客 - 185A】无序组数 (思维,