单源最短路算法
Dijkstra
算法原理
首先将所有点分为两类,确定和没确定最短路的点。
首先我们可以知道,起点只能通过已经确定最短路的点去到其他点,所以我们可以不断的确定距离起点最近的点的路径长度,再用这个确定的点更新其他点到起点的最短路距离。
此时找最近点为 \(O(n)\),更新邻接点为 \(O(m)\),每次因为确定一个点,所以时间复杂度为 \(O(n^2+m)\)。
我们发现找距离最近的点其实是一个动态找最值的过程,考虑堆优化,用堆维护点,时间复杂度:\(O((n+m)\log n)\)。
证明
假定当前未确定最短路的点中,离起点最近的点为 \(y\),与已确定最短路的点 \(x\) 之间有直接连边,那么这时如果可以更新起点到 \(y\) 最短路的话,那么就可以直接更新。
用反证法证明,如果起点到 \(x\) 的最短路经过一个没确定最短路的点 \(b\) 且点 \(b\) 与已经确定最短路的点 \(a\) 有直接连边,由于起点到点 \(a\) 的长度不小于到点 \(x\) 的长度,并且 \(b\) 到 \(y\) 的距离非负,所以这个路径并不会更短。
注意
- Dijkstra 不能处理负权图。
也很容易举出反例:
在上面这张图里如果以点 \(2\) 为起点,那么会先更新点 \(1\),但是考虑到点 \(3\) 可能会更新点 \(1\),且到点 \(1\) 的最短路径为 \(2\to 3\to 1\),所以Dijkstra 不能处理负权图。
同理 Dijkstra 也不能处理最长路。
P4779 【模板】单源最短路径(标准版)代码
#include <bits/stdc++.h>
using namespace std;
using PII = pair <int, int>;
const int N = 1e5 + 5;
struct V {
vector <PII> e;
int d = INT_MAX;
} v[N];
priority_queue <PII, vector <PII>, greater <PII> > q;
int n, m, s;
void Dijkstra() {
for (q.push({0, s}); q.size();) {
PII p = q.top();
q.pop()
if (p.first < v[p.second].d) {
v[p.second].d = p.first;
for (PII e : v[p.second].e) {
q.push({p.first + e.second, e.first});
}
}
}
}
int main() {
cin >> n >> m >> s;
for (int i = 1, x, y, w; i <= m; i ++) {
cin >> x >> y >> w;
v[x].e.push_back({y, w});
}
Dijkstra();
for (int i = 1; i <= n; i ++) {
cout << v[i].d << " ";
}
}
bellman_ford
在最短路中,一条边至多被松弛 \(n-1\) 次,所以我们枚举 \(n-1\) 轮,在枚举每条边判断边的两端是否可以松弛,其余跟 Dijkstra 差不多。
时间复杂度 \(O(NM)\)。
P3371 【模板】单源最短路径(弱化版 70 分)代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5e5 + 5;
int n, m, s, dis[N];
vector<tuple<int, int, int>> e;
signed main() {
cin >> n >> m >> s;
for (int i = 1, u, v, w; i <= m; i++) {
cin >> u >> v >> w, e.push_back({u, v, w});
}
fill(dis + 1, dis + n + 1, 2147483647), dis[s] = 0;
for (int T = 1; T < n; T++) {
for (auto [u, v, w] : e) {
dis[v] = min(dis[v], dis[u] + w);
}
}
for (int i = 1; i <= n; i++) {
cout << dis[i] << ' ';
}
}
SPFA
bellman_ford 的队列优化版本。
只有松弛过的点才能松弛其他点,用队列记录每一个被松弛的点,每次取出队头,松弛他的邻接点,如果松弛成功就压入队列,如果一个点已经在队列里了,那么就不用再进队。
注意每次取出队头时,标记取消。
P3371 【模板】单源最短路径(弱化版)
#include <bits/stdc++.h>
#define int long long
using namespace std;
using PII = pair<int, int>;
const int N = 5e5 + 5;
int n, m, s, h = 1, t, q[N * 10];
struct V {
vector<PII> e;
int d = INT_MAX, fl = false;
} v[N];
void Record(int x, int d) {
if (v[x].d > d) {
v[x].d = d;
!v[x].fl && (q[++t] = x, v[x].fl = 1);
}
}
signed main() {
cin >> n >> m >> s;
for (int i = 1, x, y, w; i <= m; i++) {
cin >> x >> y >> w;
v[x].e.push_back({y, w});
}
for (Record(s, 0); h <= t; h++) {
v[q[h]].fl = 0;
for (auto [nxt, w] : v[q[h]].e) {
Record(nxt, v[q[h]].d + w);
}
}
for (int i = 1; i <= n; i++) {
cout << v[i].d << " ";
}
}
作者:Livedreamyhy
出处:https://www.cnblogs.com/Livedreamyhy/p/18191158
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App