最短路模板
最短路
Floyed
时间复杂度:\(O(n^3)\)
标准弗洛伊德算法,三重循环。循环结束之后 \(dis[i][j]\) 存储的就是点 \(i\) 到点 \(j\) 的最短距离。
需要注意循环顺序不能变:第一层枚举中间点,第二层和第三层枚举起点和终点。
题目多数时间限制为 \(1s\),因此一般适合 \(n\le 300\) 的情况。
#include <bits/stdc++.h>
using namespace std;
const int N = 310, INF = 0x3f3f3f3f;
int n, m;
int dis[N][N]; //存储两点之间的最短距离
int main() {
memset(dis, 0x3f, sizeof(dis));
cin >> m >> n;
for (int i = 0; i < m; i++) {
int a, b, c;
cin >> a >> b >> c;
dis[a][b] = dis[b][a] = min(c, dis[a][b]);
}
// floyd 算法核心
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]);
cout << dis[1][n] << endl;
return 0;
}
Dijkstra
朴素的 Dijkstra
时间复杂度:\(O(n^2)\)
朴素的 Dijkstra 算法,图用邻接矩阵存储,基于贪心的思想,每次暴力循环找距离起点最近的,且未被标记过的点进行松弛操作。
不能求最长路,不能求有负边权的图的最短路。
#include <bits/stdc++.h>
using namespace std;
const int N = 310, INF = 0x3f3f3f3f;
int n, m;
int g[N][N], dis[N]; // 邻接矩阵, dis[i]表示起点到 i 的最短距离
bool vis[N]; // vis[i] 标记结点 i 的最短距离是否已确定
void dijkstra(int s) { // 求 s 到其他结点的最短距离
memset(dis, 0x3f, sizeof(dis));
dis[s] = 0;
// 循环 n 次,每次确定一个结点的最短距离并标记
for (int i = 0; i < n; i++) {
int k = -1, mind = INF;
for (int j = 1; j <= n; j++) {
if (!vis[j] && dis[j] < mind) {
mind = dis[j];
k = j;
}
}
if (k == -1) break; // 图不连通,终止
vis[k] = true;
for (int j = 1; j <= n; j++) {
if (!vis[j] && dis[j] > dis[k] + g[k][j])
dis[j] = dis[k] + g[k][j];
}
}
}
int main()
{
cin >> m >> n;
`
for (int i = 0; i < m; i++)
{
int a, b, c;
cin >> a >> b >> c;
g[a][b] = g[b][a] = min(g[a][b], c); // 去重边
}
dijkstra();
cout << dis[n] << endl;
return 0;
}
带权有向图 Dijkstra + 堆优化 + vector 存图
用小根堆维护起点到所有点的距离。时间复杂度是 \(O(mlogn)\)
直接使用 STL 中的 priority_queue
,将所有修改过的点直接插入堆中,堆中会有重复元素,但堆中元素总数不会大于 \(m\) 。
struct Node {
int id, dis;
bool operator < (const Node &A) const {
return dis > A.dis;
}
};
int dis[N];
bool vis[N];
void dij(int s) {
priority_queue<Node> q;
memset(dis, 0x3f, sizeof(dis));
memset(vis, false, sizeof(vis));
dis[s] = 0;
vis[s] = true;
q.emplace(Node{s, 0});
Node nd;
while (!q.empty()) {
nd = q.top();
q.pop();
s = nd.id;
if (vis[s]) continue;
vis[s] = true;
for (const auto &e : edge[s]) {
if (!vis[e.to] && dis[e.to] > dis[s] + e.w) {
dis[e.to] = dis[s] + e.w;
q.emplace(Node{e.to, dis[e.to]});
}
}
}
}
SPFA
对 Bellman-ford 算法的优化版本,可以处理存在负边权的最短路问题。
最坏情况下的时间复杂度是 \(O(nm)\),但对于随机图,SPFA 算法的运行效率非常高,期望运行时间是 \(O(km)\),其中 \(k\) 是常数。
但需要注意的是,在网格图中,SPFA 算法的效率比较低,如果边权为正,则尽量使用 Dijkstra 算法。
邻接矩阵存图+SPFA
const int N = 1010;
const int INF = 0x3f3f3f3f;
int g[N][N], dis[N]; //邻接矩阵存图
int t, n, m, whole;
void SPFA(int s) {
bool vis[N] = {};
queue<int> que;
memset(vis, 0, sizeof(vis));
memset(dis, 0x3f, sizeof(dis));
//起点初始化,入队并标记
dis[s] = 0;
vis[s] = true;
que.push(s);
while (!que.empty()) {
s = que.front(); que.pop();
vis[s] = 0; //标记出队
for (int i = 1; i <= n; i++) {
if (g[s][i] < INF && dis[s] + g[s][i] < dis[i]) {
dis[i] = dis[s] + g[s][i];
if (!vis[i]) { //不在队列中,则加入
que.push(i);
vis[i] = 1;
}
}
}
}
}
邻接矩阵存图+SPFA判负环
用小根堆维护起点到所有点的距离。时间复杂度是 \(O(mlogn)\)
直接使用 STL 中的 priority_queue
,将所有修改过的点直接插入堆中,堆中会有重复元素,但堆中元素总数不会大于 \(m\) 。
const int N = 1010;
const int INF = 0x3f3f3f3f;
int g[N][N], dis[N]; //邻接矩阵存图
int cnt[N]; //cnt记录每个结点入队次数
int n, m;
bool SPFA(int s) { //bfs版spfa判负环
bool vis[N] = {};
queue<int> que;
memset(vis, 0, sizeof(vis));
memset(cnt, 0, sizeof(cnt));
memset(dis, 0x3f, sizeof(dis));
dis[s] = 0;
vis[s] = true;
que.push(s);
cnt[s]++; //起点入队次数加1
while (!que.empty()) {
s = que.front(); que.pop();
vis[s] = 0; //标记出队
for (int i = 1; i <= n; i++) {
if (g[s][i] < INF && dis[s] + g[s][i] < dis[i]) {
dis[i] = dis[s] + g[s][i];
if (!vis[i]) {
que.push(i);
cnt[i]++; //入队数次加1
if (cnt[i] >= n) //不存在负环的话,每个结点最多入队n-1次
return true; //存在负环
vis[i] = 1;
}
}
}
}
return false; //不存在负环
}