【模板】最短路徑(迪傑斯特拉、SPFA、弗洛伊德)
阿新 • • 發佈:2019-01-28
迪傑斯特拉演算法(Dijkstra's Algorithm)
解決單源最短路問題的優秀演算法,堆優化後時間複雜度降到O((m+n)logn)。
#include<iostream> #include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<queue> #include<vector> #define FOR(i,x,y) for(int i=(x);i<=(y);i++) #define DOR(i,x,y) for(int i=(x);i>=(y);i--) #define N 10003 #define M 500003 #define INF 2147483647 using namespace std; int n,m; int dis[N]; struct edge { int to,cost; }; vector<edge>E[N]; //這裡以鄰接表為例 struct node { //走到at節點的最短路為path int at,path; bool operator<(const node &_)const { return path>_.path; //path小的先出 } }; void dijkstra(int s) { FOR(i,1,n)dis[i]=INF; dis[s]=0; priority_queue<node>q; while(!q.empty())q.pop(); //使用前測試STL是好習慣 q.push((node){s,0}); //把起點先加入佇列 while(!q.empty()) { node now=q.top();q.pop(); //取距離最小的節點 int u=now.at; if(dis[u]<now.path)continue; //剪枝,如果已經找到更小的dis[u]那麼只需要鬆弛那一次就夠了 FOR(i,0,(int)E[u].size()-1) { int v=E[u][i].to,w=E[u][i].cost; if(dis[v]>dis[u]+w) { dis[v]=dis[u]+w; q.push((node){v,dis[v]}); } } } return; } int main() { int s; scanf("%d%d%d",&n,&m,&s); FOR(i,1,m) { int u,v,w; scanf("%d%d%d",&u,&v,&w); E[u].push_back((edge){v,w}); } dijkstra(s); FOR(i,1,n)printf("%d ",dis[i]); printf("\n"); return 0; }
Dj演算法的流程如上,其實不難理解。從源點出發開始鬆弛,然後尋找沒有作過起點的節點中距離最小的,作為新的起點進行下一輪操作。特別要提示的是,Dj演算法只適用於正權圖,對於有負邊出現的圖不好使,這也能解釋為什麼凡是標紅的點一定是找到最短路的。
而堆優化其實是優化了尋找距離最小節點這步,從程式碼中不難看出,如果出現負邊,就會出現死迴圈。因為已經作過起點的節點還會作無數次起點。
SPFA演算法(Shortest Path Faster Algorithm)
實際上是Bellman-Ford演算法的優化,時間複雜度為O(kn),k一般小於等於2n.
#include<iostream> #include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<queue> #define FOR(i,x,y) for(int i=(x);i<=(y);i++) #define DOR(i,x,y) for(int i=(x);i>=(y);i--) #define N 10003 #define M 500003 #define INF 2147483647 using namespace std; int n,m,tot; int head[N],dis[N]; struct edge { int next,to,w; //E[i].next是邊i的“下一條”邊的序號 }E[M]; void add(int u,int v,int w) //這裡以前向星為例 { E[++tot].next=head[u]; //head[u]是以u為起點的“第一條”邊的序號 E[tot].to=v; E[tot].w=w; head[u]=tot; return; } void spfa(int s) { bool vis[N]={0}; vis[s]=1; //vis[i]表示i是否在佇列中 queue<int>q; while(!q.empty())q.pop(); q.push(s); FOR(i,1,n)dis[i]=INF; dis[s]=0; while(!q.empty()) { int u=q.front();q.pop(); vis[u]=0; for(int i=head[u];i!=0;i=E[i].next) //前向星遍歷 { int v=E[i].to,w=E[i].w; if(dis[v]>dis[u]+w) { dis[v]=dis[u]+w; if(!vis[v]) { vis[v]=1; q.push(v); } } } } return; } int main() { int s; scanf("%d%d%d",&n,&m,&s); for(int i=1;i<=m;i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); add(u,v,w); } spfa(s); FOR(i,1,n)printf("%d ",dis[i]); printf("\n"); return 0; }
spfa比較淺顯,這裡重點介紹前向星。
前向星是一種存圖方式,它將起點相同的邊互相用next鏈起(而鄰接表是直接將它們存在同一個vector中),每次加入新的邊(u,v)都會鏈上上一條起點為u的邊,然後更新起點為u的“第一條”邊。
這種儲存方式,使得訪問邊的順序與輸入順序相反,不過這也沒關係。
弗洛伊德演算法(Floyd's Algorithm)
弗洛伊德演算法是解決多源最短路問題的優秀演算法,基於動態規劃,此演算法的精妙之處在於核心程式碼只有4-5行
弗洛伊德時間複雜度達到O(n³),當n在100以內時求多源最短路問題可用它很方便的解決。#include<iostream> #include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #define FOR(i,x,y) for(int i=(x);i<=(y);i++) #define DOR(i,x,y) for(int i=(x);i>=(y);i--) #define N 1003 #define M 500003 #define INF 2147483647 using namespace std; int n,m; int dis[N][N]; //此演算法用鄰接矩陣存邊 int main() { int s; scanf("%d%d%d",&n,&m,&s); FOR(i,1,n)FOR(j,1,n)dis[i][j]=INF; FOR(i,1,n)dis[i][i]=0; FOR(i,1,m) { int u,v,w; scanf("%d%d%d",&u,&v,&w); dis[u][v]=min(dis[u][v],w); } FOR(k,1,n) //Floyd FOR(i,1,n) FOR(j,1,n) if(dis[i][k]!=INF && dis[k][j]!=INF) //這裡因為題目需要,事實上如果2INF<MaxInt,這行程式碼可以省去 dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); FOR(i,1,n)printf("%d ",dis[s][i]); printf("\n"); return 0; }