一、概述
次短路算法是指除了最短路之外长度最小的路径,一般来说在一个图中,最短路和次短路可以同时被一个最短路算法求出。
本文侧重于介绍如何在堆优化版的 Dijkstra 算法中加入次短路算法计算的方法,从而实现同时计算最短路和次短路的功能。由于对优化版的 Dijkstra 算法的时间复杂度为
O
(
(
n
+
m
)
log
m
)
O((n+m)\log m)
O((n+m)logm) ,所以这个算法的时间复杂度将是最短路的时间复杂度的一个常数倍数,也就是
O
(
k
(
n
+
m
)
log
m
)
(
k
∈
N
+
,
k
≥
1
)
O(k(n+m)\log m)\text{ }(k\in\mathbb{N^+},k\ge 1)
O(k(n+m)logm) (k∈N+,k≥1) 。
次短路算法可以用于计算最短路算法之外的次优策略,需要时应该根据题目的条件来进行判断与分析。
二、具体实现方法
前置知识: Dijkstra 算法的具体实现步骤可以参考 这篇文章 ,本文要求读者对 Dijkstra 算法完全掌握。
首先,我们按照 Dijkstra 算法的步骤先定义一个优先队列(priority_queue
) ,用来存放三个目标值:最短路的长度,当前节点的编号,以及当前的值是最短路还是次短路。三个值的类型分别是:整数,整数,布尔型。
代码实例如下,我们先定义一个结构体和一个运算符,用来存储所有的信息。(注意, priority_queue
自带的是一个小根堆,需要手动重载 <
运算符来进行排序,否则会出现意想不到的问题)。
struct Node
{
int to, dis, ty;
// 这里, to 表示当前的节点编号
// dis 表示当前的最短路、次短路距离的值
// ty 表示当前的值是最短路还是次短路,用 0 或者 1 表示,比如 0 表示最短路, 1 表示次短路等
bool operator<(Node t) const // 这个 const 一定要加,记住即可
{
return dis > t.dis; // 这里是用小于符号来替代大于符号,方便 priority_queue 的定义
}
};
priority_queue<Node> q; // 这里已经重载了 < 运算符,所以小根堆默认按照我们给定的算法排序
然后,我们开始 Dijkstra 次短路算法的正式部分。
开始之前,我们要定义一些状态和变量。设
d
(
x
,
0
)
d(x,0)
d(x,0) 表示从源点到
x
x
x 的最短路,
d
(
x
,
1
)
d(x,1)
d(x,1) 表示从源点到
y
y
y 的最短路,
e
e
e 是一个向量矩阵,表示使用 vector<pair<int, int>
存储的给定的图。具体如下:
typedef pair<int, int> pii;
#define wi first
#define to second
const int N = 1e5 + 10; // 根据实际情况自己调整
int d[N][2]; // 含义如上
vector<pii> e[N]; // 使用上面构建的内容存图
初始化:首先设源点为 s s s ,将源点入队:
q.push({s, 0, 0});
然后,初始化
d
d
d 状态的值为 int
范围内的
∞
\infty
∞ ,然后令
d
(
s
,
0
)
=
0
d(s,0)=0
d(s,0)=0 。
memset(d, 0x3f, sizeof(d));
d[s][0] = 0;
开始算法的计算部分:使用 while
循环,每次从队列中取出当前最小的值,并将当前的最小值扩展到邻近的所有点,并更新他们的最小值。如果一个点被当前点成功更新了最小值,则更新当前点的最小值和次小值。具体操作:先将次小值设置为原来的最小值,再将最小值更新为当前找到的值。简单说明如下:
设当前放入并即将更新的值为
t
t
t 。假设
t
<
d
(
x
,
0
)
t<d(x,0)
t<d(x,0) ,则我们需要将
d
(
x
,
0
)
d(x,0)
d(x,0) 更新成
t
t
t 。由于此时
t
<
d
(
x
,
0
)
,
d
(
x
,
0
)
<
d
(
x
,
1
)
t<d(x,0),d(x,0)<d(x,1)
t<d(x,0),d(x,0)<d(x,1) ,所以我们的次小值将更新成原来的
d
(
x
,
0
)
d(x,0)
d(x,0) 。但因为更新后的
d
(
x
,
0
)
=
t
d(x,0)=t
d(x,0)=t ,所以我们需要提前件
d
(
x
,
1
)
=
d
(
x
,
0
)
d(x,1)=d(x,0)
d(x,1)=d(x,0) ,这样来进行变换。
此处,
d
(
x
,
0
)
d(x,0)
d(x,0) 和
d
(
x
,
1
)
d(x,1)
d(x,1) 的计算方法 Dijkstra 算法相同,即设当前的点为
x
x
x ,之前访问的点为
p
p
p ,则状态转移方程如下:
d
(
x
,
0
)
=
min
∀
p
,
x
→
,
x
,
p
∈
e
{
d
(
p
,
0
)
+
p
,
x
→
}
{
d
(
x
,
1
)
=
min
∀
p
,
x
→
,
x
,
p
∈
e
{
d
(
p
,
1
)
+
p
,
x
→
}
[if
d
(
p
,
0
)
+
p
,
x
→
>
d
(
x
,
0
)
]
d
(
x
,
1
)
=
min
∀
p
,
x
→
,
x
,
p
∈
e
{
d
(
p
,
0
)
+
p
,
x
→
}
[if
d
(
p
,
0
)
+
p
,
x
→
<
d
(
x
,
0
)
]
d(x,0)=\min_{\forall \overrightarrow{p,x},x,p\in e}\{d(p,0)+\overrightarrow{p,x}\} \\ \begin{cases} d(x,1)&=\min_{\forall\overrightarrow{p,x},x,p\in e}\{d(p,1)+\overrightarrow{p,x}\} &\text{[if }d(p,0)+\overrightarrow{p,x}>d(x,0)]\\ d(x,1)&=\min_{\forall\overrightarrow{p,x},x,p\in e}\{d(p,0)+\overrightarrow{p,x}\} &\text{[if }d(p,0)+\overrightarrow{p,x}<d(x,0)] \end{cases}
d(x,0)=∀p,x,x,p∈emin{d(p,0)+p,x}{d(x,1)d(x,1)=min∀p,x,x,p∈e{d(p,1)+p,x}=min∀p,x,x,p∈e{d(p,0)+p,x}[if d(p,0)+p,x>d(x,0)][if d(p,0)+p,x<d(x,0)]
最后,将更新后的最小值和次小值入队更新其他的点。如果一个点被当前点成功更新的次小值,则更新当前点的次小值,直接改变次小值即可,无需复杂的操作步骤。这样,我们就成功设置了一个最小值和次小值的计算过程。
// 使用 Dijkstra 算法计算最短路和次短路
while (!q.empty())
{
Node t = q.top();
q.pop();
for (pii i : e[t])
{
if (d[i.to][0] > t.dis + i.wi)
{
d[i.to][1] = d[i.to][0]; // 先更新次小值,以免最小值被覆盖
q.push({i.to, d[i.to][1], 1});
d[i.to][0] = t.dis + i.wi; // 更新最小值
q.push({i.to, d[i.to][0], 0});
}
else if (d[i.to][1] > t.dis + i.wi)
{
d[i.to][1] = t.dis + i.wi; // 更新次小值即可
q.push({i.to, d[i.to][1], 1});
}
}
}
三、完整代码实现
这样,我们就实现了计算最短路和次短路的算法。代码如下,可自行添加输出答案的部分:
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
#define wi first
#define to second
const int N = 1e5 + 10;
struct Node
{
int to, dis, ty;
bool operator<(Node t) const
{
return dis > t.dis;
}
};
priority_queue<Node> q;
int d[N][2];
vector<pii> e[N];
int n, m, s;
void print()
{
// ...
}
int main()
{
cin >> n >> m;
for (int i = 1, a, b, c; i <= m; ++i)
{
cin >> a >> b >> c;
e[a].push_back({c, b});
e[b].push_back({c, a});
}
cin >> s;
q.push({s, 0, 0});
memset(d, 0x3f, sizeof(d));
d[s][0] = 0;
while (!q.empty())
{
Node t = q.top();
q.pop();
for (pii i : e[t])
{
if (d[i.to][0] > t.dis + i.wi)
{
d[i.to][1] = d[i.to][0];
q.push({i.to, d[i.to][1], 1});
d[i.to][0] = t.dis + i.wi;
q.push({i.to, d[i.to][0], 0});
}
else if (d[i.to][1] > t.dis + i.wi)
{
d[i.to][1] = t.dis + i.wi;
q.push({i.to, d[i.to][1], 1});
}
}
}
print();
return 0;
}