最短路演算法總結
阿新 • • 發佈:2020-11-22
樸素版Dijkstra(適用於 無負權變的稠密圖)
#include <iostream> #include <cstring> using namespace std; const int N = 510; int n, m; int dis[N], g[N][N]; // dis陣列存放起點到各點之間的最短距離 bool st[N]; // st陣列判斷這個點是否已經加入了最短路徑的集合 int Dijkstra(int start, int end){ memset(dis, 0x3f, sizeof dis); dis[start] = 0; // 起點到起點的距離為0,同時不能吧st[起點]變成1, 不然不會更新與起點相鄰的點 for(int i = 0; i < n; ++i){ int t = -1; // 在未加入最短路集合的點中找到距離起點最小的點 for(int j = 1; j <= n; ++j){ if(!st[j] && (t == -1 || dis[t] > dis[j])) t = j; } //將找到的點加入集合 st[t] = true; // 由這個點來更新起點到其他點的距離 for(int j = 1; j <= n; ++j){ dis[j] = min(dis[j], dis[t] + g[t][j]); } } return dis[end]; } int main(){ memset(g, 0x3f, sizeof g); scanf("%d%d", &n, &m); while(m --){ int a, b, c; scanf("%d%d%d", &a, &b, &c); g[a][b] = min(g[a][b], c); } int ans = Dijkstra(1, n); if(ans == 0x3f3f3f3f) printf("-1"); else printf("%d", ans); return 0; }
Bellman-Ford演算法時間輔助度O(n*m) 幾乎不會使用,除了少數情況如這題,要求最多k條邊的最短路徑,適用於稀疏圖,可以處理負權邊,同時可以判斷是否存在負環,但是一般不用他來判斷負環,用佇列優化版的Bellman-Ford也就是spfa演算法來判斷負環。
#include <iostream> #include <cstring> using namespace std; const int N = 10010, M = 510; int dist[M], backup[M]; // backup 為dist的拷貝陣列 // 對建邊沒有條件,所以直接用一個結構體陣列來儲存 struct Edge{ int a, b, v; }edges[N]; int n, m, k; // 全名Bellman_ford演算法,幾乎不用,除了這道題,因為k表示的最多經過k條邊的最短路徑當 int bf(int start, int end){ memset(dist, 0x3f, sizeof dist); dist[start] = 0; for(int i = 0; i < k; ++i){ memcpy(backup, dist, sizeof dist); // 為了讓每次在下面for迴圈中dist不被反覆更新,我們讓backup為上一次迴圈的dist陣列 for(int j = 0; j < m; ++j){ auto t = edges[j]; dist[t.b] = min(dist[t.b], backup[t.a] + t.v); // 用上一次的dist陣列來更新 } } return dist[end]; } int main(){ scanf("%d%d%d", &n, &m, &k); for(int i = 0; i < m; ++i){ int a, b, c; scanf("%d%d%d", &a, &b, &c); edges[i] = {a, b, c}; } int ans = bf(1, n); if(ans > 0x3f3f3f3f / 2) puts("impossible"); // 因為可能存在負權變所以正無窮的邊也可能被更新 else printf("%d", ans); return 0; }
佇列優化版Bellman-Ford也叫Spfa,適用於稀疏圖,可處理負權邊以及判斷負環,時間複雜度O(m),但有一種將時間複雜度卡到O(n*m)的方法這時就有可能超時,但一般不會卡
#include <iostream> #include <cstring> #include <queue> using namespace std; const int N = 100010; int h[N], ne[N], e[N], w[N], idx; int n, m; int dis[N], st[N]; queue<int> q; // 鏈式前向星核心程式碼 void add(int a, int b, int c){ e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++; } void spfa(int start){ memset(dis, 0x3f, sizeof dis); // 初始化 q.push(start); // 將起點放入佇列 st[start] = true; // 在佇列中的點為true,否則為false dis[start] = 0; while(q.size()){ auto t = q.front(); q.pop(); st[t] = false; // 遍歷出隊的臨邊 for(int i = h[t]; ~i; i = ne[i]){ int j = e[i]; // 如果可以更新,並且佇列中沒有,那麼就將這個點也加入隊列當中 if(dis[j] > dis[t] + w[i]){ dis[j] = dis[t] + w[i]; if(!st[j]){ q.push(j); st[j] = true; } } } } } int main(){ scanf("%d%d", &n, &m); memset(h, -1, sizeof h); while(m --){ int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c); } spfa(1); if(dis[n] == 0x3f3f3f3f) puts("impossible"); else printf("%d", dis[n]); return 0; }
spfa判斷是否存在負環
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 100010;
int h[N], e[N], ne[N], idx, w[N], dis[N], cnt[N];
bool st[N];
int n, m;
queue<int> q;
void add(int a, int b, int c){
w[idx] = c, e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 當一個點被入隊的次數大於能於n次則肯定存在負環,cnt陣列表示到達當前這個點最短路的邊數,
//如果cnt[x]>=n,說明至少經過了n條邊,即n+1個點,由抽屜原理可知顯然有兩個點重複,即存在負環
bool spfa(){
for(int i = 1; i <= n; ++ i){
q.push(i);
st[i] = i;
}
while(q.size()){
auto t = q.front();
q.pop();
st[t] = false;
for(int i = h[t]; ~i; i = ne[i]){
int j = e[i];
if(dis[j] > dis[t] + w[i]){
dis[j] = dis[t] + w[i];
cnt[j] = cnt[t] + 1;
if(cnt[j] >= n) return true;
if(!st[j]){
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
int main(){
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
for(int i = 0; i < m; ++i){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
if(spfa()) puts("Yes");
else puts("No");
return 0;
}