Shortest path
-
Dijkstra
不含负权边的有向或无向图的单源最短路。Greedy.
为何负权边不可:Dijkstra是贪心算法,而负权边会导致贪心算法失败,因为可能要绕远路。
a---3---b | | 4___c__-2
时间复杂度分析:
\[
\displaylines{
T(n) = O(E)*dk_Q + O(V)*em_Q
}
\]
\(dk_Q\) means Decrease Key, we need to sort all edges once in the whole algo.
\(em_Q\) means Extract Min, for each vertex, we calculate the next shortest edge once.
min heap | brute | |
---|---|---|
dk | logV (insert in heap) | 1 (no sorting) |
em | logV (pop heap) | V^2 (one by one) |
So a min heap Dijkstra's time complexity is \(O((E+V)logV) \approx O(ElogV)\)
If considering we keep old edges in the heap, the operation may at worst be \(O(logE)\). So \(O((E+V)logE)\) is also right.
-
Bellman-Ford
含负权边的有向图的单源最短路。Dynamic Programming.
可以判断负环。
时间复杂度分析:\(O(EV)\)
-
SPFA
队列优化的BF算法,可以判断负环。
期望时间复杂度\(O(E)\)。
-
Floyd
\(O(V^3)\)
全局,动规,支持负权。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
using namespace std;
const int maxv = 30005;
const int maxe = 150005;
const int maxw = 0x7fffffff;
int N, M;
struct edge {
int f, t, w;
edge() {}
edge(int u, int v, int w) :f(u), t(v), w(w) {}
};
vector<edge> edges;
vector<int> G[maxv];
// directed
void insert_edge(int a, int b, int c) {
edges.push_back(edge(a, b, c));
int s = edges.size() - 1;
G[a].push_back(s);
}
int dist[maxv], vis[maxv];
bool cmp(int a, int b) {
return dist[a] > dist[b];
}
void dijkstra(int s) {
for (int i = 1; i <= N; i++) {
dist[i] = maxw;
vis[i] = 0;
}
dist[s] = 0;
priority_queue<int, vector<int>, bool(*)(int, int)> que(cmp);
que.push(s);
while (!que.empty()) {
int u = que.top(); que.pop();
if (vis[u]) continue;
vis[u] = 1;
for (int i = 0; i < G[u].size(); i++) {
int e = G[u][i];
int v = edges[e].t;
if (vis[v]) continue;
if (dist[v] > dist[u] + edges[e].w) {
dist[v] = dist[u] + edges[e].w;
que.push(v);
}
}
}
}
bool bellmanford(int s) {
for (int i = 1; i <= N; i++) dist[i] = maxw;
dist[s] = 0;
// run (N - 1) times
for (int k = 1; k < N; k++) {
// loop all edges
for (int i = 0; i < edges.size(); i++) {
int u = edges[i].f;
int v = edges[i].t;
if (dist[v] > dist[u] + edges[i].w)
dist[v] = dist[u] + edges[i].w;
}
}
// run 1 more time to detect negative loop.
for (int i = 0; i < edges.size(); i++) {
int u = edges[i].f;
int v = edges[i].t;
if (dist[v] > dist[u] + edges[i].w)
return true; // if the dist can still be updated, there must be a negative loop.
}
return false;
}
bool spfa(int s) {
for (int i = 1; i <= N; i++) dist[i] = maxw, vis[i] = 0; // vis is used as cnt
dist[s] = 0;
queue<int> q; // stack is also OK.
q.push(s);
while (!q.empty()) {
int p = q.front(); q.pop();
for (int i = 0; i < G[p].size(); i++) {
edge& e = edges[G[p][i]];
if (dist[e.t] > dist[e.f] + e.w) {
dist[e.t] = dist[e.f] + e.w;
q.push(e.t);
if (vis[e.t]++ >= N) return true; // if a point is enqueued for N times, there must be a negative loop.
}
}
}
return false;
}
int main() {
cin >> N >> M;
int a, b, c;
for (int i = 0; i < M; i++) {
cin >> a >> b >> c;
insert_edge(a, b, c);
}
dijkstra(1);
//bellmanford(1);
//s
cout << dist[N] << endl;
}
Examples
-
Currency Exchange
最长路+正环判断,设置源点为持有货币V,其他点初始化0,普通的按照规则计算路径即可。注意判断正环并不需要传播回源点,因为BF只额外传播一轮,源点有可能没有被更新到,但是只要有正环,多轮之后一定可以传回源点。
#include <iostream> #include <cstring> #include <algorithm> #include <vector> #include <queue> #include <stack> #include <set> using namespace std; const int maxv = 105; const int maxe = 1005; struct edge { int f, t; double w, c; edge() {} edge(int u, int v, double w, double c) :f(u), t(v), w(w),c(c) {} }; vector<edge> edges; vector<int> G[maxv]; // directed void insert_edge(int a, int b, double w, double c) { edges.push_back(edge(a, b, w, c)); int s = edges.size() - 1; G[a].push_back(s); } int N, M, S; double V; int a, b; double c, d, e, f; double dist[maxv]; bool bellmanford(int s, double v) { memset(dist, 0, sizeof(dist)); dist[s] = v; for (int k = 1; k < N; k++) { bool flag = false; for (int u = 1; u <= N; u++) { for (int i = 0; i < G[u].size(); i++) { int e = G[u][i]; int v = edges[e].t; if (dist[v] < (dist[u] - edges[e].c) * edges[e].w) { dist[v] = (dist[u] - edges[e].c) * edges[e].w; flag = true; } } } if (!flag) break; // optim } for (int u = 1; u <= N; u++) { for (int i = 0; i < G[u].size(); i++) { int e = G[u][i]; int v = edges[e].t; if (dist[v] < (dist[u] - edges[e].c)*edges[e].w) return true; } } return false; } int main() { cin >> N >> M >> S >> V; for (int i = 0; i < M; i++) { cin >> a >> b >> c >> d >> e >> f; insert_edge(a, b, c, d); insert_edge(b, a, e, f); } cout << (bellmanford(S, V)?"YES":"NO") << endl; }
-
Warmholes
spfa version.
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstdio> #include <cstring> #include <string> #include <queue> #include <stack> #include <vector> #include <algorithm> #define P pair<int,int> using namespace std; const int maxn = 505; const int inf = 0x3f3f3f3f; int N, M, W; struct edge { int f, t, w; edge() {} edge(int f, int t, int w) :f(f), t(t), w(w) {} }; vector<edge> G[maxn]; int dist[maxn], times[maxn]; bool spfa(int s) { fill(dist, dist + N + 1, inf); fill(times, times + N + 1, 0); dist[s] = 0; queue<int> q; // stack is also OK. q.push(s); while (!q.empty()) { int p = q.front(); q.pop(); for (int i = 0; i < G[p].size(); i++) { edge& e = G[p][i]; if (dist[e.t] > dist[e.f] + e.w) { dist[e.t] = dist[e.f] + e.w; q.push(e.t); if (times[e.t]++ >= N) return true; } } } return false; } int T, x, y, w; int main() { scanf("%d", &T); while (T--) { scanf("%d%d%d", &N, &M, &W); for (int i = 1; i <= N; i++) G[i].clear(); for (int i = 0; i < M; i++) { scanf("%d%d%d", &x, &y, &w); G[x].push_back(edge(x, y, w)); G[y].push_back(edge(y, x, w)); } for (int i = 0; i < W; i++) { scanf("%d%d%d", &x, &y, &w); G[x].push_back(edge(x, y, -w)); } cout << (spfa(1) ? "YES" : "NO") << endl; } }