本课时有配套视频讲解,购买后即可观看(永久有效)
Dijkstra求单源次短路径
一、课上练习
编程练习
- 单源次短路径:L49984
二、知识总结
✨ 核心思想
次短路径是指从源点 到目标点 的所有路径中,长度严格大于最短路径的最小值。换句话说,次短路是"第二短"的路径。
核心方法:对 Dijkstra 进行扩展,为每个顶点维护两个距离——最短距离 dist1[v] 和次短距离 dist2[v]。在松弛操作时,不仅更新最短距离,还维护次短距离。
✨ 算法原理
在标准 Dijkstra 中,每个顶点只维护一个最短距离。为了求次短路,我们做以下修改:
- 两个距离数组:
dist1[v](最短)和dist2[v](严格次短)。 - 松弛规则扩展:当考察边 时,计算新距离 :
- 如果 :更新次短
dist2[v] = dist1[v],再更新最短dist1[v] = d,入堆。 - 否则如果 且 :更新
dist2[v] = d,入堆。
- 如果 :更新次短
- 每个顶点可出堆两次:最短和次短各一次。用计数数组
cnt[v]记录出堆次数,超过 2 次跳过。
关键点:这里不再使用 vis 数组做"确定后不再访问"的判断,而是允许每个顶点被处理最多 2 次。
✨ 代码实现
1#include <bits/stdc++.h>
2using namespace std;
3
4const int MAXN = 200005;
5const long long INF = 1e18;
6
7vector<pair<int, int>> adj[MAXN]; // 邻接表
8long long dist1[MAXN]; // 最短距离
9long long dist2[MAXN]; // 严格次短距离
10int cnt[MAXN]; // 每个顶点被确定的次数
11int n, m;
12
13void dijkstra(int s) {
14 for (int i = 1; i <= n; i++) {
15 dist1[i] = dist2[i] = INF;
16 cnt[i] = 0;
17 }
18 dist1[s] = 0;
19
20 // 小根堆:{距离, 顶点}
21 priority_queue<pair<long long, int>,
22 vector<pair<long long, int>>,
23 greater<>> pq;
24 pq.push({0, s});
25
26 while (!pq.empty()) {
27 auto [d, u] = pq.top();
28 pq.pop();
29
30 cnt[u]++;
31 // 每个顶点最多处理2次(最短和次短)
32 if (cnt[u] > 2) continue;
33
34 for (auto [v, w] : adj[u]) {
35 long long nd = d + w;
36
37 if (nd < dist1[v]) {
38 // 新距离比最短还短
39 dist2[v] = dist1[v]; // 原最短变为次短
40 dist1[v] = nd;
41 pq.push({nd, v});
42 } else if (nd > dist1[v] && nd < dist2[v]) {
43 // 严格大于最短,且比当前次短更短
44 dist2[v] = nd;
45 pq.push({nd, v});
46 }
47 }
48 }
49}
50
51int main() {
52 ios::sync_with_stdio(false);
53 cin.tie(nullptr);
54
55 int s, t;
56 cin >> n >> m >> s >> t;
57
58 for (int i = 0; i < m; i++) {
59 int u, v, w;
60 cin >> u >> v >> w;
61 adj[u].push_back({v, w});
62 adj[v].push_back({u, w}); // 无向图
63 }
64
65 dijkstra(s);
66
67 if (dist2[t] == INF)
68 cout << -1 << endl; // 不存在次短路
69 else
70 cout << dist2[t] << endl;
71
72 return 0;
73}1#include <bits/stdc++.h>
2using namespace std;
3
4const int MAXN = 200005;
5const long long INF = 1e18;
6
7vector<pair<int, int>> adj[MAXN];
8long long dist[MAXN][2]; // dist[v][0]=最短, dist[v][1]=次短
9int n, m;
10
11void dijkstra(int s) {
12 for (int i = 1; i <= n; i++) {
13 dist[i][0] = dist[i][1] = INF;
14 }
15 dist[s][0] = 0;
16
17 // {距离, 顶点, 类型(0=最短/1=次短)}
18 priority_queue<tuple<long long, int, int>,
19 vector<tuple<long long, int, int>>,
20 greater<>> pq;
21 pq.push({0, s, 0});
22
23 while (!pq.empty()) {
24 auto [d, u, type] = pq.top();
25 pq.pop();
26
27 // 如果当前距离已经过时,跳过
28 if (d > dist[u][type]) continue;
29
30 for (auto [v, w] : adj[u]) {
31 long long nd = d + w;
32
33 if (nd < dist[v][0]) {
34 dist[v][1] = dist[v][0];
35 dist[v][0] = nd;
36 pq.push({dist[v][0], v, 0});
37 pq.push({dist[v][1], v, 1});
38 } else if (nd > dist[v][0] && nd < dist[v][1]) {
39 dist[v][1] = nd;
40 pq.push({nd, v, 1});
41 }
42 }
43 }
44}✨ 执行示例
图:4 个顶点,无向图,求 1 到 4 的次短路。
边:1-2(1), 2-3(2), 3-4(3), 1-3(4), 2-4(7)
最短路径:,长度 = 1 + 2 + 3 = 6。
次短路径候选:
- :长度 = 4 + 3 = 7
- :长度 = 1 + 7 = 8
次短路径 = 7()。
算法执行过程:
| 步骤 | 堆顶(d,u) | dist1 变化 | dist2 变化 |
|---|---|---|---|
| 1 | (0,1) | dist1[2]=1, dist1[3]=4 | - |
| 2 | (1,2) | dist1[3]=3, dist1[4]=8 | dist2[3]=4 |
| 3 | (3,3) | dist1[4]=6 | dist2[4]=8 |
| 4 | (4,3) | - | dist2[4]=7 |
| 5 | (6,4) | 确定4的最短=6 | - |
| 6 | (7,4) | 确定4的次短=7 | - |
结果:dist2[4] = 7。
✨ 解题步骤详解
- 建图:读入边,建邻接表。
- 初始化:所有 dist1 和 dist2 设为 INF,源点 dist1 设为 0。
- 扩展 Dijkstra:每个顶点允许出堆 2 次,维护两个距离。
- 松弛时注意:
- 更新 dist1 时,原 dist1 的值"降级"成 dist2。
- 更新 dist2 时,新距离必须严格大于 dist1。
- 输出 dist2[t] 即为次短路。
变体题型:
- 非严格次短路:将
nd > dist1[v]改为nd >= dist1[v](允许相等)。 - 第 K 短路:维护 K 个距离,每个顶点最多出堆 K 次。
- 次短路径计数:额外维护 cnt1 和 cnt2 记录路径条数。
✨ 常见错误
- 次短距离初始化忘记设为 INF:dist2 也必须初始化为 INF。
- 未要求"严格大于":题目要求严格次短时,
nd == dist1[v]的情况不能更新 dist2。 - vis 数组使用错误:不能像普通 Dijkstra 那样用 vis 数组"一次确定",因为每个点需要处理两次。
- cnt 数组上限设错:应允许每个点出堆 2 次,不是 1 次。
- 更新 dist1 时忘记同步更新 dist2:当 dist1 被更新时,旧的 dist1 值应赋给 dist2。
- 堆中过时条目过多导致 TLE:检查 cnt > 2 后 continue 是必要的剪枝。
✨ 算法评价
| 指标 | 分析 |
|---|---|
| 时间复杂度 | ,与标准 Dijkstra 相同级别 |
| 空间复杂度 | |
| 适用条件 | 边权非负,求严格/非严格次短路 |
| 实现难度 | 中等,在 Dijkstra 基础上扩展 |
| 拓展性 | 可扩展为第 K 短路(每点维护 K 个距离) |
次短路径是 Dijkstra 的经典扩展应用,体现了"维护多个状态"的思想,在竞赛中是常见考点。