解决求节点间最短路径的问题。模板比较固定,请自行查找材料学习算法思想,按照模板多写几遍就会写了。
5.8.4.1 Dijkstra 算法
非负权图的单源最短路稳妥方法。
// Dijkstra 使用堆优化,比较通用的版本#include <bits/stdc++.h>using namespace std;const int N = 504;const int M = 1e5 + 4;typedef pair<int, int> P; // pair<边权值, 终点>,默认按第一个元素排序,顺序不能颠倒!priority_queue<P, vector<P>, greater<P> > q; // 堆,C++11前,两个右尖括号必须用空格隔开,否则编译为>>位右移运算vector<P> g[N]; // 邻接表P p;int dist[N]; // dist[i] 表示起点到 i 的距离bool vis[N];int n, m;// @s 为起点void dj(int s) {memset(dist, 0x3f, sizeof dist); // 起点不能到达其他点,距离为无穷dist[s] = 0;p = P(0, s);q.push(p);while (!q.empty()) {p = q.top();q.pop();int x = p.second;if (vis[x]) {continue;}vis[x] = true;for (int i=0; i<g[x].size(); ++i) {p = g[x][i];int y = p.second, w = p.first;if (dist[y] > dist[x] + w) {dist[y] = dist[x] + w;q.push(P(dist[y], y));}}}}int main() {cin >> n >> m;for (int i=0; i<m; ++i) {int x;cin >> x;cin >> p.second >> p.first;g[x].push_back(p);}dj(1);if (dist[n] != 0x3f3f3f3f) {cout << dist[n] << endl;} else {cout << -1 << endl;}return 0;}
时间复杂度:#card=math&code=O%28m%5Clog%20m%29&id=yS0gr),
为边数。
代码中 memset 语句填入了 0x3f ,这意味着把数组的每一位设成 0x3f,最终把整个数组初始化为无穷大 0x3f3f3f3f。类似地,memset 有三种优雅的初始化赋值方案:
memset(vis, 0, sizeof vis); // 初始化为 0memset(ans, -1, sizeof ans); // 初始化为 -1memset(dist, 0x3f, sizeof dist); // 初始化为 0x3f3f3f3f
使用 memset 语句初始化数组,具有比 for 循环赋值更高的效率。
5.8.4.2 SPFA
支持负权图,可检测环的单源最短路 “快速算法” 。Bellman-Ford 算法的队列优化形式,在中国多称为SPFA(Shortest Path Faster Algorithm)。
// SPFA#include <bits/stdc++.h>using namespace std;const int N = 1e5 + 4;const int inf = 0x3f3f3f3f;struct Edge {int y, w; // 终点,边权Edge() {}Edge(int a, int b): y(a), w(b) {}} e;vector<Edge> g[N];int dist[N];bool vis[N];int cnt[N];queue<int> q;int n, m;// s 为起点,返回 true 时表示有环路void spfa(int s) {memset(dist, 0x3f, sizeof dist);dist[s] = 0;q.push(s);while (!q.empty()) {int x = q.front();q.pop();vis[x] = false;for (int i=0; i<g[x].size(); ++i) {e = g[x][i];int y = e.y;int w = e.w;if (dist[y] > dist[x] + w) {dist[y] = dist[x] + w;cnt[y] = cnt[x] + 1;if (cnt[y] >= n) return true;if (!vis[y]) {q.push(y);vis[y] = true;}}}}return false;}int main() {cin >> n >> m;for (int i=0; i<m; ++i) {int x, y, w;cin >> x >> y >> w;g[x].push_back(Edge(y, w));}spfa(1);if (dist[n] == inf) {cout << "impossible" << endl;} else {cout << dist[n] << endl;}return 0;}
平均时间复杂度:#card=math&code=O%28km%29&id=tBx9B) ,
为一个小常数,
为边数。
最坏时间复杂度:#card=math&code=O%28nm%29&id=lxEYV) ,
为边数。
SPFA 在题目告知无环的情况,可以不写 cnt[] 及关联代码。这个算法在一般的非负权图上经常可以有胜过 Dijkstra 算法的效率,但最坏情况下(出题人有意制造针对数据)会超时。关于这一趣闻,可以参阅网上资料 「关于SPFA,它死了」。
5.8.4.3 Floyd 算法
全源最短路算法,可求任意两个结点之间的最短路,唯一弱点是不能处理负环。在稠密图的情况下,时间复杂度优于 次 Dijkstra 算法。因此,模板的实现使用了邻接矩阵存储图,需要注意初始化和防止重边。
// Floyd Shortest Path
#include <iostream>
#include <cstring>
using namespace std;
const int N = 300;
int g[N][N];
int n, m;
int main() {
cin >> n >> m;
// init
memset(g, 0x3f, sizeof g);
for (int i=0; i<n; ++i) {
g[i][i] = 0;
}
// Read
for (int i=0; i<m; ++i) {
int x, y, w;
cin >> x >> y >> w;
g[x][y] = min(g[x][y], w); // 防止重边
}
// Floyd
for (int k=0; k<n; ++k) {
for (int i=0; i<n; ++i) {
for (int j=0; j<n; ++j) {
g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
}
}
}
// Print
for (int i=0; i<n; ++i) {
for (int j=0; j<n; ++j) {
cout << g[i][j] << ',';
}
cout << endl;
}
return 0;
}
时间复杂度:#card=math&code=O%28n%5E3%29&id=sbjQS),
为点数。
Floyd 算法的核心其实就是三层 for 循环,内部的语句也很好理解——如果从 点通过
点再到
点,比直接到
点更近,则把
,
两点距离
更新。然而这三层循环的层次,循环变量的改变方向绝不那么简单。事实上,它是一种「动态规划」的思想。我们会在后文进行讨论。
练习:蓝桥 算法训练 最短路
