SPFA演算法
演算法複雜度
SPFA 演算法是 Bellman-Ford演算法 的佇列優化演算法的別稱,通常用於求含負權邊的單源最短路徑,以及判負權環。
SPFA一般情況複雜度是O(m)最壞情況下複雜度和樸素 Bellman-Ford 相同,為O(nm)。
n為點數,m為邊數
spfa也能解決權值為正的圖的最短距離問題,且一般情況下比Dijkstra演算法還好
演算法步驟
queue <– 1 while queue 不為空 (1) t <– 隊頭 queue.pop() (2)用 t 更新所有出邊 t –> b,權值為w queue <– b (若該點被更新過,則拿該點更新其他點)
程式碼實現
題目:https://www.acwing.com/problem/content/description/853/
#include<bits/stdc++.h> using namespace std; const int maxn=2e5+10; typedef long long ll; ll n,m; typedef pair<int, int> PII; int h[maxn],e[maxn],w[maxn],ne[maxn],idx; int dist[maxn]; bool st[maxn]; void add(int x,int y,int c) { //權值記錄 w[idx]=c; //終點邊記錄 e[idx]=y; //儲存編號為idx的邊的前一條邊的編號 ne[idx]=h[x]; //代表以x為起點的邊的編號,這個值會發生變化 h[x]=idx++; } ll spfa() { ll i,j; memset(dist,0x3f,sizeof(dist)); dist[1]=0; queue<int> q; //將起點加入 q.push(1); //標記已在集合 st[1]=true; while(q.size()) { int t=q.front(); q.pop(); //彈出後,不在集合 st[t]=false; for(i=h[t];i!=-1;i=ne[i]) { //獲得終點 j=e[i]; //判斷距離 if(dist[j]>dist[t]+w[i]) { //更新距離 dist[j]=dist[t]+w[i]; //判斷終點是否在集合 if(!st[j]) { //加到集合,繼續更新他到其他點的最短距離 q.push(j); st[j]=true; } } } } //如果說原點到終點n的距離還是無窮,則代表到達不了 if(dist[n]==0x3f3f3f3f) return -1; else return dist[n]; } int main() { ll i,j; cin>>n>>m; //初始化h陣列為-1,目的是為ne陣列賦值 memset(h,-1,sizeof(h)); while(m--) { int x,y,z; cin>>x>>y>>z; //加邊 add(x,y,z); } ll ans=spfa(); if(ans==-1) cout<<"impossible"; else cout<<ans; return 0; }
SPFA判斷負環
求負環方法
統計當前每個點的最短路中所包含的邊數,如果某點的最短路所包含的邊數大於等於n,則也說明存在環。
演算法步驟
①初始化要將所有點都插入到佇列中
②增加一個cnt陣列,來記錄走的邊個數
③若dist[j] > dist[t] + w[i],則表示從t點走到j點能夠讓權值變少,因此進行對該點j進行更新,並且對應cnt[j] = cnt[t] + 1,往前走一步
注意:該題是判斷是否存在負環,並非判斷是否存在從1開始的負環,因此需要將所有的點都加入佇列中,更新周圍的點
程式碼實現
題目:https://www.acwing.com/problem/content/description/854/
#include<bits/stdc++.h> using namespace std; const int maxn=2e5+10; typedef long long ll; ll n,m; typedef pair<int, int> PII; int h[maxn],e[maxn],w[maxn],ne[maxn],idx; int dist[maxn],cnt[maxn]; bool st[maxn]; void add(int x,int y,int c) { //權值記錄 w[idx]=c; //終點邊記錄 e[idx]=y; //儲存編號為idx的邊的前一條邊的編號 ne[idx]=h[x]; //代表以x為起點的邊的編號,這個值會發生變化 h[x]=idx++; } bool spfa() { ll i,j; queue<int> q; //將所有點加入佇列 for(i=1;i<=n;i++) { q.push(i); st[i]=true; } while(q.size()) { int t=q.front(); q.pop(); st[t]=false; for(i=h[t];i!=-1;i=ne[i]) { j=e[i]; //dist陣列不用初始化,是因為如果為負的就進行更新,才能找出負環 if(dist[j]>dist[t]+w[i]) { dist[j]=dist[t]+w[i]; //邊數更新 cnt[j]=cnt[t]+1; //大於n-1條邊,代表有負環 if(cnt[j]>=n) return true; if(!st[j]) { q.push(j); st[j]=true; } } } } return false; } int main() { ll i,j; cin>>n>>m; //初始化h陣列為-1,目的是為ne陣列賦值 memset(h,-1,sizeof(h)); while(m--) { int x,y,z; cin>>x>>y>>z; //加邊 add(x,y,z); } //堆優化版的Dijkstra if(spfa()) cout<<"Yes"; else cout<<"No"; return 0; }
Floyd演算法
原理
多源匯最短路問題
演算法步驟
①初始化d
②k, i, j 去更新d
程式碼實現
題目:https://www.acwing.com/problem/content/description/856/
#include<bits/stdc++.h> using namespace std; int n,m,k; const int maxn=220,INF=0x3f3f3f3f; int d[maxn][maxn]; void floyd() { for(int k=1;k<=n;k++) { for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) d[i][j]=min(d[i][j],d[i][k]+d[k][j]); } } } int main() { int i,j; cin>>n>>m>>k; for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { if(i==j) d[i][j]=0; else d[i][j]=INF; } } while(m--) { int x,y,z; cin>>x>>y>>z; d[x][y]=min(d[x][y],z); } floyd(); while(k--) { int x,y; cin>>x>>y; if(d[x][y]>INF/2) cout<<"impossible"<<endl; else cout<<d[x][y]<<endl; } return 0; }
最短路總結