Floyd | Dijkstra | Bellman-Ford | Johnson |
---|---|---|---|
每对结点之间的最短路 | 单源最短路 | 单源最短路 | 每对结点之间的最短路 |
没有负环的图 | 非负权图 | 任意图(可以判定负环是否存在) | 没有负环的图 |
O ( n 3 ) O(n^3) O(n3) | O ( m log m ) O(m \log m) O(mlogm) | O ( n m ) O(nm) O(nm) | O ( n m log m ) O(nm \log m) O(nmlogm) |
*注:这里的 Dijkstra 指单调队列优化的,未优化的 Dijkstra 时间复杂度为 O ( n 2 ) O(n^2) O(n2)
// Floyd 邻接矩阵
#include
#include
#define N 302
using namespace std;
int dis[N][N];
int n, m;
int u, v, w;
int main() {
memset(dis,63,sizeof(dis));
cin >> n >> m;
for(int i = 1; i <= m; ++i) {
cin >> u >> v >> w;
dis[u][v] = w;
}
for(int i = 1; i <= n; ++i) {
dis[i][i] = 0;
}
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= n; ++j) {
cout << dis[i][j] << " ";
}
cout << endl;
}
cout << endl;
for(int k = 1; k <= n; ++k) {
for(int i = 1; i <= n; ++i) {
for(int j = 1; j<= n; ++j) {
dis[i][j] = min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
}
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= n; ++j) {
cout << dis[i][j] << " ";
}
cout << endl;
}
}
/*
4 5
1 2 12
1 3 16
1 4 14
2 3 7
3 4 9
*/
//Luogu P3371 Dijkstra 邻接表
#include
#include
#include
#define N 10004
const int inf = 0x7f7f7f7f;
using namespace std;
int n, m, s;
int u, v, w;
struct Edge {
int v, w;
Edge(int v_, int w_):v(v_), w(w_) {
}
};
vector<Edge> g[N];
int dis[N];
bool vis[N];
void dijkstra(int st = 1) {
for(int i=1;i<=n;i++) {
dis[i]=0x7fffffff;
}
dis[st] = 0;
memset(vis,false,sizeof(vis));
for(int i = 1; i <= n; ++i) {
int minn = inf;
for(int j = 1; j <= n; ++j) {
if(!vis[j] && (minn == inf || dis[j] < dis[minn])) {
minn = j;
}
}
if(minn == inf) {
return ;
}
vis[minn] = true;
for(vector<Edge>::iterator iter = g[minn].begin(); iter != g[minn].end(); iter++) {
if(!vis[iter->v]) {
dis[iter->v] = min(dis[iter->v], dis[minn] + iter->w);
}
}
}
}
int main() {
cin >> n >> m >> s;
for(int i = 1; i <= m; ++i) {
cin >> u >> v >> w;
g[u].push_back(Edge(v,w));
}
dijkstra(s);
for(int i = 1; i <=n; ++i) {
cout << dis[i] << " ";
}
return 0;
}
//Luogu P4779 Dijkstra + priority_queue 链式前向星
#include
#include
#include
#define M 200005
#define N 100005
using namespace std;
int n, m, s;
int u, v, w;
struct Edge {
int v, w, next;
} e[M];
int head[N], cnt=0;
struct Node {
int u, d;
bool operator <(const Node& x) const {
return d > x.d;
}
};
int dis[N];
void addedge(int u, int v, int w) {
e[++cnt].v = v;
e[cnt].w = w;
e[cnt].next = head[u];
head[u] = cnt;
}
void Dijkstra(int s) {
for(int i = 1; i <= n; ++i) dis[i] = 2147483647;
dis[s]=0;
priority_queue<Node> q;
q.push((Node){
s,0});
while(!q.empty()) {
Node tmp = q.top();
q.pop();
int u = tmp.u, d = tmp.d;
if(d != dis[u]) continue;
for(int i = head[u]; i; i = e[i].next) {
int v = e[i].v, w = e[i].w;
if(dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
q.push((Node){
v,dis[v]});
}
}
}
}
int main() {
scanf("%d%d%d",&n,&m,&s);
for(int i = 1; i <= m; ++i) {
scanf("%d%d%d",&u,&v,&w);
addedge(u, v, w);
}
Dijkstra(s);
for(int i = 1; i <= n; ++i) {
printf("%d ",dis[i]);
}
return 0;
}
//Bellman-Ford
#include
const int INF = 0x3f3f3f3f;
using namespace std;
int n, m;
int u[100], v[100], w[100];
int dis[100];
int main() {
cin >> n >> m;
for(int i = 1; i <= m; i++) {
cin >> u[i] >> v[i] >> w[i];
}
for(int i = 1; i <= n; i++) {
dis[i] = INF;
}
dis[1] = 0;
for(int k = 1; k <= n - 1; k++) {
for(int i = 1; i <= m; i++) {
if(dis[v[i]] > dis[u[i]] + w[i]) {
dis[v[i]] = dis[u[i]] + w[i];
}
}
}
for(int i = 1; i <= n; i++) {
cout << dis[i] << " ";
}
return 0;
}
/*
5 5
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
*/
优化
如果在新一轮的松弛中数组 dis 没有发生变化,则可以提前跳出循环。
#include
const int INF = 0x3f3f3f3f;
using namespace std;
int n, m;
int u[100], v[100], w[100];
int dis[100];
int main() {
cin >> n >> m;
for(int i = 1; i <= m; i++) {
cin >> u[i] >> v[i] >> w[i];
}
for(int i = 1; i <= n; i++) {
dis[i] = INF;
}
dis[1] = 0;
for(int k = 1; k <= n - 1; k++) {
int check = 0; // 用来标记本轮松弛操作中数组 dis 是否会发生更新
for(int i = 1; i <= m; i++) {
if(dis[v[i]] > dis[u[i]] + w[i]) {
dis[v[i]] = dis[u[i]] + w[i];
check = 1;
}
}
if(check == 0) {
break;
}
}
int flag = 0;
for(int i = 1; i <= m; i++) {
if(dis[v[i]] > dis[u[i]] + w[i]) {
flag = 1;
}
}
if(flag == 1) {
cout << "此图有负权回路\n";
} else {
for(int i = 1; i <= n; i++) {
cout << dis[i] << " ";
}
}
return 0;
}
即 Shortest Path Faster Algorithm。
关于SPFA
- 它死了
知乎 - 如何看待 SPFA 算法已死这种说法?
//SPFA 链式前向星
#include
#include
#include
using namespace std;
const int N = 100005;
const int M = 200005;
int n, m;
int s, t;
struct Edge {
int to, next, w;
} e[M];
int head[N], cnt;
int dis[N];
int In[N];
bool vis[N];
void add_edge(int u, int v, int w) {
e[++cnt].to = v;
e[cnt].next = head[u];
e[cnt].w = w;
head[u] = cnt;
}
bool Spfa(int s) {
memset(vis,false,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
memset(In,0,sizeof(In));
queue<int> q;
q.push(s);
vis[s] = true;
dis[s] = 0;
while(!q.empty()) {
int u = q.front();
q.pop();
vis[u] = false;
for(int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if(dis[v] > dis[u] + e[i].w) {
dis[v] = dis[u] + e[i].w;
if(!vis[v]) {
q.push(v);
vis[v] = true;
if(++In[v] > n) return false;
}
}
}
}
return true;
}
int main() {
cin >> n >> m;
cin >> s >> t;
for(int i = 1; i <= m; ++i) {
int u, v, w;
cin >> u >> v >> w;
add_edge(u, v, w);
}
if(!Spfa(s)) {
cout << "FAIL!" << endl;
} else {
cout << dis[t] << endl;
}
return 0;
}
/*
5 5
1 5
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
*/
/*
2 2
1 2
1 2 1
2 1 -2
*/
除了队列优化(SPFA)之外,Bellman-Ford 还有其他形式的优化,这些优化在部分图上效果明显,但在某些特殊图上,最坏复杂度可能达到指数级。
SPFA + DFS
//P3385 SPFA dfs (TLE on #9) [洛谷 - P3385 【模板】负环](https://www.luogu.com.cn/problem/P3385)
#include
#include
#include
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 2003;
const int M = 6003;
int T;
int n, m;
struct Edge {
int to, next, w;
} e[M];
int head[N], cnt;
int dis[N];
bool instack[N];
bool flag;
void add_edge(int u, int v, int w) {
e[++cnt].to = v;
e[cnt].next = head[u];
e[cnt].w = w;
head[u] = cnt;
}
void Spfa(int u) {
instack[u]=true;
for(int i = head[u]; i; i=e[i].next) {
int v = e[i].to;
if(dis[v] > dis[u] + e[i].w) {
dis[v] = dis[u] + e[i].w;
if(!instack[v]) {
Spfa(v);
} else {
flag = true;
return;
}
}
}
instack[u]=false;
}
int main() {
cin >> T;
while(T--) {
flag = false;
memset(instack,false,sizeof(instack));
memset(e,0,sizeof(e));
memset(head,0,sizeof(head));
cnt = 0;
cin >> n >> m;
for(int i = 1; i <= m; ++i) {
int u, v, w;
cin >> u >> v >> w;
if(w >= 0) {
add_edge(u, v, w);
add_edge(v, u, w);
} else {
add_edge(u, v, w);
}
}
for(int i = 1; i <= n; i++) {
dis[i] = INF;
}
dis[1] = 0;
Spfa(1);
if(flag) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
}
return 0;
}
新建一个虚拟节点(设它的编号为 0 0 0),从这个点向其他所有点连一条边权为 0 0 0 的边。
用 Bellman-Ford 算法求出从 0 0 0 号点到其他所有点的最短路,记为 h i h_i hi 。
假如存在一条从 u u u 点到 v v v 点,边权为 w w w 的边,则将该边的边权重新设置为 w + h u − h v w+h_u-h_v w+hu−hv 。
接下来以每个点为起点,跑 n n n 轮 Dijkstra 算法即可求出任意两点间的最短路了。
//P5905 Johnson [洛谷 - P5905 【模板】Johnson 全源最短路](https://www.luogu.com.cn/problem/P5905)
#include
#include
#include
#define INF 1e9
using namespace std;
const int N = 5005;
const int M = 10005;
struct edge {
int v, w, next;
} e[M];
struct node {
int dis, id;
bool operator < (const node& a) const {
return dis > a.dis;
}
node(int d, int x) {
dis = d, id = x;
}
};
int head[N], vis[N], t[N];
int cnt, n, m;
long long h[N], dis[N];
void add_edge(int u, int v, int w) {
e[++cnt].v = v;
e[cnt].w = w;
e[cnt].next = head[u];
head[u] = cnt;
}
bool spfa(int s) {
queue<int> q;
memset(h,63,sizeof(h));
h[s] = 0, vis[s] = 1;
q.push(s);
while(!q.empty()) {
int u = q.front();
q.pop();
vis[u] = 0;
for(int i = head[u]; i; i = e[i].next) {
int v = e[i].v;
if(h[v] > h[u] + e[i].w) {
h[v] = h[u] + e[i].w;
if(!vis[v]) {
vis[v] = 1;
q.push(v);
t[v]++;
if(t[v] == n + 1) return false;
}
}
}
}
return true;
}
void Dijkstra(int s) {
priority_queue<node> q;
for(int i = 1; i <= n; ++i) {
dis[i] = INF;
}
memset(vis,0,sizeof(vis));
dis[s] = 0;
q.push(node(0,s));
while(!q.empty()) {
int u = q.top().id;
q.pop();
if(vis[u]) continue;
vis[u] = 1;
for(int i = head[u]; i; i = e[i].next) {
int v = e[i].v;
if(dis[v] > dis[u] + e[i].w) {
dis[v] = dis[u] + e[i].w;
if(!vis[v]) q.push(node(dis[v],v));
}
}
}
return ;
}
int main() {
cin >> n >> m;
for(int i = 1; i <= m; ++i) {
int u, v, w;
cin >> u >> v >> w;
add_edge(u, v, w);
}
for(int i = 1; i <= n; ++i) {
add_edge(0, i, 0);
}
if(!spfa(0)) {
cout << -1 << endl;
return 0;
}
for(int u = 1; u <= n; ++u) {
for(int i = head[u]; i; i = e[i].next) {
e[i].w += h[u] - h[e[i].v];
}
}
for(int i = 1; i <= n; ++i) {
Dijkstra(i);
long long ans = 0;
for(int j = 1; j <= n; ++j) {
if(dis[j] == INF) ans += j * INF;
else ans += j * (dis[j] + h[j] - h[i]);
}
cout << ans << endl;
}
return 0;
}
开一个 pre 数组,在更新距离的时候记录下来后面的点是如何转移过去的。最后递归地输出路径即可。
比如 Floyd 就要记录 p r e [ i ] [ j ] = k pre[i][j] = k pre[i][j]=k,Bellman-Ford 和 Dijkstra 一般记录 p r e [ v ] = u pre[v] = u pre[v]=u 。
/*......*/
int pre[N];
/*......*/
void print_road(int s) {
if(pre[s] != -1) {
print_road(pre[s]);
}
else {
return;
}
printf("->%d", s);
}
/*......*/
memset(pre,-1,sizeof(pre));
/*......*/
printf("%d",s);
print_road(t);
printf("\n");
/*......*/
https://oi-wiki.org/graph/shortest-path/