最短路理论 (Shortest Path Theory)
最短路问题不仅是离散数学的经典课题,更是对偶理论与路径代数 (Path Algebra) 在图结构上的完美体现。本章将从形式化松弛理论出发,建立路径拓扑的代数证明体系,并深入探讨从 Dijkstra 的贪心收敛到 Johnson 算法的势能变换。
一、 路径代数与拓扑证明 (Algebraic Topology of Paths)
为了建立系统化的证明,我们引入 (min, +) 半环 (Tropical Semiring)。
1. 代数结构:Dioid 结构
最短路问题可以抽象为矩阵乘法在 上的运算:
- 加法 :定义为 。
- 乘法 :定义为 。
- 单位元:加法单位元为 ,乘法单位元为 。
在这种代数体系下,邻接矩阵 的 次幂 的元素 恰好代表从 到 经过最多 条边的最短路径长度。
2. 形式化松弛算子与收敛性分析 (Relaxation Convergence)
定义:对于边 ,松弛操作 定义为:
二、 核心算法深度分析与收敛性
1. Dijkstra 算法:贪心收敛性证明
Dijkstra 算法本质上是在非负权图上维护一个已确定集合 。每次选取 中 最小的点,其贪心选择的正确性由非负权带来的“距离单调递增性”保证。
2. Bellman-Ford 与 SPFA:动态规划的视角
Bellman-Ford 算法可以看作是状态转移方程: 其中 表示经过最多 条边到达 的最短路。SPFA (Shortest Path Faster Algorithm) 则是其队列优化版本,利用“只有被松弛的点才可能松弛后续点”的观测。
3. Johnson 算法:势能变换 (Reweighting)
Johnson 算法通过引入势能函数 重新标定边权: 收敛性分析:
- 非负性:选择 为从超级源点到 的最短路,由三角不等式 ,得 。
- 路径不变性:对于路径 ,其新权值为 。对于固定的 ,所有路径的增量相同,不改变最短路结构。
三、 建模进阶:差分约束系统
差分约束系统是将逻辑约束转化为路径拓扑一致性校验的标准模型。
1. 连通性一致性校验
对于约束 ,连边 。
- 存在解条件:图中不含负权环。
- 逻辑一致性:若存在负权环 ,则累加不等式得 ,产生逻辑悖论。
四、 工业级 C++ 实现 (SPFA 带负环判定)
/**
* @brief SPFA 算法:支持负权边与负环判定
* 复杂度: 平均 O(kE), 最坏 O(VE)
*/
bool spfa(int s, int n, vector<long long>& dist) {
dist.assign(n + 1, INF);
vector<int> cnt(n + 1, 0);
vector<bool> in_queue(n + 1, false);
queue<int> q;
dist[s] = 0; q.push(s); in_queue[s] = true;
while (!q.empty()) {
int u = q.front(); q.pop(); in_queue[u] = false;
for (auto& e : adj[u]) {
if (dist[e.to] > dist[u] + e.w) {
dist[e.to] = dist[u] + e.w;
if (!in_queue[e.to]) {
q.push(e.to); in_queue[e.to] = true;
if (++cnt[e.to] >= n) return false; // 判定负环
}
}
}
}
return true;
}
五 精选练习与解析
练习 1:最短路计数 (Modulo )
给定无向图,求 到各点的最短路条数。
Check Solution
解析:
在 Dijkstra 过程中维护 cnt[v]。
- 若
dist[v] > dist[u] + w:更新dist[v]并设cnt[v] = cnt[u]。 - 若
dist[v] == dist[u] + w:cnt[v] = (cnt[v] + cnt[u]) % MOD。
// C++ 核心逻辑
if (dist[v] > d + w) {
dist[v] = d + w;
cnt[v] = cnt[u];
pq.push({dist[v], v});
} else if (dist[v] == d + w) {
cnt[v] = (cnt[v] + cnt[u]) % MOD;
}
练习 2:分层图建模 - 魔法飞行
在 个城市之间飞行,可以使 条航线的费用变为 。求从 到 的最小费用。
Check Solution
解析: 建立 层图。节点 表示在 点且已使用了 次免费机会。
- 层内边:。
- 层间边:。
/**
* @brief 分层图 Dijkstra 范式
*/
void solve() {
priority_queue<Node, vector<Node>, greater<Node>> pq;
dist[1][0] = 0; pq.push({1, 0, 0});
while (!pq.empty()) {
auto [u, k, d] = pq.top(); pq.pop();
if (d > dist[u][k]) continue;
for (auto& e : adj[u]) {
if (dist[e.v][k] > d + e.w) {
dist[e.v][k] = d + e.w; pq.push({e.v, k, dist[e.v][k]});
}
if (k < K && dist[e.v][k+1] > d) {
dist[e.v][k+1] = d; pq.push({e.v, k+1, dist[e.v][k+1]});
}
}
}
}
练习 3:第 K 短路问题 (A* Algorithm)
求 的第 短路径权值。
Check Solution
解析: 利用 搜索。
- 启发式函数 : 到 的真实最短路(在反图上以 为源跑一次 Dijkstra 预处理)。
- 核心结论:当汇点 第 次被弹出优先队列时,当前的 即为第 短路。
练习 4:负环判定进阶 - 01 分数规划
给定带权图,每条边有收益 和成本 ,找一个环使得 最大。
Check Solution
解析: 二分答案 。判定是否存在环满足 。 建立新图,边权为 ,跑最长路判定是否存在正环(或取反跑最短路判定负环)。
bool check(double mid) {
for (int i = 1; i <= m; ++i) {
new_w[i] = a[i] - mid * b[i];
}
return has_positive_cycle(); // SPFA 判定
}
练习 5:最短路径树 (SPT) 计数
求一个图有多少棵生成树,使得树上根节点到各点的距离等于图中的最短路距离。
Check Solution
解析: 首先跑一次源点 的最短路。 对于每个点 ,统计满足 的入边数量 。 根据乘法原理,总 SPT 数量为 。