日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

ACM算法--spfa算法--最短路算法

發(fā)布時(shí)間:2023/12/10 编程问答 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ACM算法--spfa算法--最短路算法 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

?求單源最短路的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ù)迭代下去。

算法的描述:

void spfa(s); //求單源點(diǎn)s到其它各頂點(diǎn)的最短距離for i=1 to n do { dis[i]=∞; vis[i]=false; } //初始化每點(diǎn)到s的距離,不在隊(duì)列dis[s]=0; //將dis[源點(diǎn)]設(shè)為0vis[s]=true; //源點(diǎn)s入隊(duì)列head=0; tail=1; q[tail]=s; //源點(diǎn)s入隊(duì), 頭尾指針賦初值while head<tail do {head+1; //隊(duì)首出隊(duì)v=q[head]; //隊(duì)首結(jié)點(diǎn)vvis[v]=false; //釋放對(duì)v的標(biāo)記,可以重新入隊(duì)for 每條邊(v,i) //對(duì)于與隊(duì)首v相連的每一條邊if (dis[i]>dis[v]+a[v][i]) //如果不滿足三角形性質(zhì)dis[i] = dis[v] + a[v][i] //松弛dis[i]if (vis[i]=false) {tail+1; q[tail]=i; vis[i]=true;} //不在隊(duì)列,則加入隊(duì)列}


最短路徑本身怎么輸出?
????在一個(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)是什么,輸出卻要從最前面到后面輸出,這很好辦,遞歸就可以了:?

c++ code: void printpath(int k){if (path[k]!=0) printpath(path[k]);cout << k << ' '; }pascal code: procedure printpath(k:longint);beginif path[k]<>0 then printpath(path[k]);write(k,' ');end; spfa算法模板(鄰接矩陣): c++ code: void spfa(int s){for(int i=0; i<=n; i++) dis[i]=99999999; //初始化每點(diǎn)i到s的距離dis[s]=0; vis[s]=1; q[1]=s; 隊(duì)列初始化,s為起點(diǎn)int i, v, head=0, tail=1;while (head<tail){ 隊(duì)列非空head++; v=q[head]; 取隊(duì)首元素vis[v]=0; 釋放隊(duì)首結(jié)點(diǎn),因?yàn)檫@節(jié)點(diǎn)可能下次用來(lái)松弛其它節(jié)點(diǎn),重新入隊(duì)for(i=0; i<=n; i++) 對(duì)所有頂點(diǎn)if (a[v][i]>0 && dis[i]>dis[v]+a[v][i]){ dis[i] = dis[v]+a[v][i]; 修改最短路if (vis[i]==0){ 如果擴(kuò)展結(jié)點(diǎn)i不在隊(duì)列中,入隊(duì)tail++;q[tail]=i;vis[i]=1;}}} }pascal code: procedure spfa(s:longint);var i,j,v,head,tail:longint;beginfor i:=0 to n do dis[i]:=99999999;dis[s]:=0; vis[s]:=true; q[1]:=s;head:=0;tail:= 1;while head<tail dobegininc(head);v:=q[head];vis[v]:=false;for i:=0 to n doif dis[i]>dis[v]+a[v,i] thenbegindis[i]:= dis[v]+a[v,i];if not vis[i] thenbegininc(tail);q[tail]:=i;vis[i]:=true;end;end;end;end;



【程序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)之間有多條道路,我們要保留最小的那條。
?

pascal code(鄰接矩陣): var i,n,m,s,t,x,y,z:longint; s:起點(diǎn);t:終點(diǎn)a,b:array[0..201,0..201] of longint; b[x,c]存與x相連的第c個(gè)邊的另一個(gè)結(jié)點(diǎn)yq:array[0..10001] of integer; 隊(duì)列vis:array[0..201] of boolean; 是否入隊(duì)的標(biāo)記dis:array[0..201] of longint; 到起點(diǎn)的最短路 procedure spfa(s:longint);var i,j,v,head,tail:longint;beginfillchar(q,sizeof(q),0);fillchar(vis,sizeof(vis),false);for i:=0 to n do dis[i]:=99999999;dis[s]:=0; vis[s]:=true; q[1]:=s; 隊(duì)列的初始狀態(tài),s為起點(diǎn)head:=0;tail:= 1;while head<tail do 隊(duì)列不空begininc(head);v:=q[head]; 取隊(duì)首元素vis[v] := false; 釋放結(jié)點(diǎn),一定要釋放掉,因?yàn)檫@節(jié)點(diǎn)有可能下次用來(lái)松弛其它節(jié)點(diǎn)for i:=1 to b[v,0] doif dis[b[v,i]]>dis[v]+a[v,b[v,i]] thenbegindis[b[v,i]]:=dis[v]+a[v,b[v,i]]; 修改最短路if not vis[b[v,i]] then 擴(kuò)展結(jié)點(diǎn)入隊(duì)begininc(tail);q[tail]:=b[v,i];vis[b[v,i]]:=true;end;end;end;end; beginread(n, m); //n結(jié)點(diǎn)數(shù);m邊數(shù)fillchar(a,sizeof(a),0);for i:=1 to m dobeginreadln(x,y,z); x,y一條邊的兩個(gè)結(jié)點(diǎn);z這條邊的權(quán)值if (a[x,y]<>0)and(z>a[x,y]) then continue;如果兩頂點(diǎn)間有多條邊,保留最小的一條inc(b[x,0]);b[x,b[x,0]]:=y;a[x,y]:=z; b[x,0]以x為一個(gè)結(jié)點(diǎn)的邊的條數(shù)inc(b[y,0]);b[y,b[y,0]]:=x;a[y,x]:=z;end;readln(s,t); 讀入起點(diǎn)與終點(diǎn)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=0; i<=n; i++) dis[i]=99999999;dis[s]=0; vis[s]=1; q[1]=s; 隊(duì)列的初始狀態(tài),s為起點(diǎn)int i, v, head=0, tail=1; while (head<tail){ 隊(duì)列不空head++;v=q[head]; 取隊(duì)首元素vis[v]=0; 釋放結(jié)點(diǎn),一定要釋放掉,因?yàn)檫@節(jié)點(diǎn)有可能下次用來(lái)松弛其它節(jié)點(diǎn)for(i=1; i<=b[v][0]; i++)if (dis[b[v][i]] > dis[v]+a[v][b[v][i]]){dis[b[v][i]] = dis[v]+a[v][b[v][i]]; 修改最短路if (vis[b[v][i]]==0){ 擴(kuò)展結(jié)點(diǎn)入隊(duì)tail++;q[tail]=b[v][i];vis[b[v][i]]=1;}}} } int main(){int x, y, z;cin >> n >> m; //n結(jié)點(diǎn)數(shù);m邊數(shù)for(int i=0; i<m; i++){cin >> x >> y >> z; x,y一條邊的兩個(gè)結(jié)點(diǎn);z這條邊的權(quán)值if (a[x][y]!=0 && z>a[x][y]) continue;如果兩頂點(diǎn)間有多條邊,保留最小的一條b[x][0]++; b[x][b[x][0]]=y; a[x][y]=z; b[x,0]以x為一個(gè)結(jié)點(diǎn)的邊的條數(shù)b[y][0]++; b[y][b[y][0]]=x; a[y][x]=z;}cin >> s >> t; 讀入起點(diǎn)與終點(diǎn)spfa(s);if (dis[t]!=99999999) cout << dis[t] << endl;else cout << -1 << endl;return 0; }


?

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)行下一次迭代。

使用dfs優(yōu)化spfa算法: pascal code: procedure spfa(s:longint);var i:longint;beginfor i:=1 to b[s,0] do //b[s,0]是從頂點(diǎn)s發(fā)出的邊的條數(shù)if dis[b[s,i]]>dis[s]+a[s,b[s,i]] then //b[s,i]是從s發(fā)出的第i條邊的另一個(gè)頂點(diǎn)begindis[b[s,i]]:=dis[s]+a[s,b[s,i]];spfa(b[s,i]);end;end; C++ code: void spfa(int s){for(int i=1; i<=b[s][0]; i++) //b[s,0]是從頂點(diǎn)s發(fā)出的邊的條數(shù)if (dis[b[s][i]>dis[s]+a[s][b[s][i]]){ //b[s,i]是從s發(fā)出的第i條邊的另一個(gè)頂點(diǎn)dis[b[s][i]=dis[s]+a[s][b[s][i]];spfa(b[s][i]);} }

???????? 相比隊(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)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。