【学习笔记】最短路 Dijkstra & BF & Floyd & Johnson
最短路
「Floyd」
简介
Floyd全名Floyd-Warshall, 又名插点法,是一个基于动态规划的求最短路的算法
罗伯特·弗洛伊德(Robert W. Floyd,1936-2001)老yeye的算法(无关紧要)
就是在带权图中求最短路的算法
核心思路
特点
需要基于邻接矩阵来做
用来求任意两个结点之间的最短路
复杂度比较高,但是常数小,容易实现(只有三个 for).
适用于任何图,不管有向无向,边权正负,但是最短路必须存在.(不能有个负环)(必须带权的)
实现
注意到又名插点法
我们就对于dis[i][j]我们可以枚举k作为中心转点
也就是dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j])
再来枚举i, j, k就可以了
复杂度明显\(O(n ^ 3)\)
代码
for (k = 1; k <= n; k++) {
for (x = 1; x <= n; x++) {
for (y = 1; y <= n; y++) {
f[k][x][y] = min(f[k - 1][x][y], f[k - 1][x][k] + f[k - 1][k][y]);
}
}
}
空间复杂度是 \(O(n ^ 2)\)
可能说我的想法和别人的不一样,我直接从i, j入手,但是这样更易懂,对。
已知一个有向图中任意两点之间是否有连边,要求判断任意两点是否连通.\
该问题即是求 图的传递闭包.
只是边权变成0/1
g[i][j] = g[i][j] | (g[i][k] & g[k][j]);
怎么样
「Bellman–Ford」
简介
Bellman–Ford 算法是一种基于松弛(relax)操作的最短路算法,可以求出有负权的图的最短路,并可以对最短路不存在的情况进行判断
「SPFA」就是他的一种常数优化实现
实现
这个算法基于松弛
对于边 \((u, v)\) 松弛操作需要满足三角不等式
对应: \(dis[v] = min(dis[v], dis[u] + w(u, v))\)
这就是「三角不等式」的魅力
Bellman–Ford 算法所做的,就是不断尝试对图上每一条边进行松弛.我们每进行一轮循环,就对图上所有的边都尝试进行一次松弛操作,当一次循环中没有成功的松弛操作时,算法停止.
负环
如果有负环,那么他就会一直松弛
(自己想一下)
所以,我们只需要算个cnt就行了
需要注意的是,以 \(S\) 点为源点跑
Bellman–Ford算法时,如果没有给出存在负环的结果,只能说明从 \(S\)点出发不能抵达一个负环,而不能说明图上不存在负环.
因此如果需要判断整个图上是否存在负环,最严谨的做法是建立一个超级源点,向图上每个节点连一条权值为 \(0\) 的边,然后以超级源点为起点执行
Bellman–Ford算法.
「SPFA」
我们观察到在「Bellman–Ford」中有许多没用的「松弛」
浪费了大量的时间!!/_ \
经过观察,我们发现只有上一次「松弛」的「邻居」才能「松弛」
我们只需要用queue维护「邻居」们
就OK了
SPFA 也可以用于判断 \(s\) 点是否能抵达一个负环,只需记录最短路经过了多少条边,当经过了至少 \(n\)条边时,说明 \(s\) 点可以抵达一个负环。
但是在某个NOI上,
SPFA 卒
警告大家:
在没有负权边时最好使用 Dijkstra 算法,不然会被卡掉
在有负权边且题目中的图没有特殊性质时,若 SPFA 是标算的一部分,题目不应当给出 Bellman–Ford 算法无法通过的数据范围,
「Bellman–Ford」 的超级优化
让后就一些牛逼的人类:
from fstqwq
除了队列优化(SPFA)之外,Bellman–Ford 还有其他形式的优化,这些优化在部分图上效果明显,但在某些特殊图上,最坏复杂度可能达到指数级.
- 堆优化:将队列换成堆,与 Dijkstra 的区别是允许一个点多次入队.在有负权边的图可能被卡成指数级复杂度.
- 栈优化:将队列换成栈(即将原来的 BFS 过程变成 DFS),在寻找负环时可能具有更高效率,但最坏时间复杂度仍然为指数级.
- LLL 优化:将普通队列换成双端队列,每次将入队结点距离和队内距离平均值比较,如果更大则插入至队尾,否则插入队首.
- SLF 优化:将普通队列换成双端队列,每次将入队结点距离和队首比较,如果更大则插入至队尾,否则插入队首.
- D´Esopo–Pape 算法:将普通队列换成双端队列,如果一个节点之前没有入队,则将其插入队尾,否则插入队首.
「Dijkstra」
简介
Dijkstra算法由荷兰计算机科学家 E. W. Dijkstra 于 1956 年发现,1959 年公开发表.是一种求解 非负权图 上单源最短路径的算法.
实现
dijkstra本质上的思想是贪心,它只适用于不含负权边的图.- 我们把点分成两类,一类是已经确定最短路径的点,称为"白点",另一类是未确定最短路径的点,称为"蓝点"
dijkstra的流程如下:-
- 初始化\(dis[start] = 0\),其余节点的\(dis\)值为无穷大.
-
- 找一个\(dis\)值最小的蓝点\(x\),把节点\(x\)变成白点.
-
- 遍历x的所有出边\((x,y,z)\),若\(dis[y]>dis[x]+z\),则令\(dis[y]=dis[x]+z\)
-
- 重复
2,3两步,直到所有点都成为白点.
- 重复
- 时间复杂度为\(O(n^2)\)
这就是红黑点染色思想
负权图于Dijkstra
之前提到过,Dijkstra 不能用于负权图
为什么不能处理负权图?
我们观察Dijkstra的思想:
- 维护一个 “已确定最短路径的节点集合”。
- 每次从这个集合的邻接点中,挑选一个距离起点路径最短的节点加入集合,并认为这个距离就是它的最终最短距离。
关键在于:一旦一个节点被标记为“已确定”(从优先队列中弹出),算法就不会再回头去更新它。因为它基于一个假设:从已确定集合出发,到任何未确定节点的距离,不会比当前已找到的路径更短。
负权边意味着“走得更远”可能反而“代价更小”。这彻底破坏了上述假设。
我们还有一种思考:
- 正因为图只有正边权时贪心才能生效
- 否则就有可能WA
Dijkstra算法正确性证明
(接下来我会对每一个难懂的话做解释)
命题:
图 G 的所有边权非负,源点为 \(s\)。
算法结束时,对所有节点 \(v\),有 \(dist[v] = d(s, v)\)。
先看这个:
在每一步中,从 \(V \ S\) 中选择 \(dist\) 值最小的节点 \(u\),必有 \(dist[u] = d(s, u)\)。
-
假设存在一条从 \(s\) 到 \(u\) 的真正最短路径 \(P\),使得 \(len(P) = d(s, u) < dist[u]\)。
有一条路径比这条更短
-
路径 P 从 s ∈ S 出发,终点是 \(u ∈ V / S\),因此路径上至少有一个点在离开 \(S\) 的时刻。
你走的所有路径中肯定有一条不在现在知道的路径中的 (\(S\))
-
将 P 分解为:
s → ... → x → y → ... → u。-
由于边权非负,所以有:
-
d(s, y) ≤ d(s, u) < dist[u]
-
-
根据算法:
-
因为 \(x ∈ S\),算法已处理过边 \((x, y)\),所以:
dist[y] ≤ dist[x] + w(x, y) = d(s, x) + w(x, y) = d(s, y) -
由于 \(y ∈ V / S\) 且 \(u\) 是 \(V / S\) 中 \(dist\) 最小的:
dist[u] ≤ dist[y]
-
-
串联不等式:
text
dist[u] ≤ dist[y] ≤ d(s, y) ≤ d(s, u) < dist[u]得出 \(dist[u] < dist[u]\),矛盾!!!!
因此假设不成立,\(dist[u] = d(s, u)\)。
「Johnson」
简介
Johnson是一个全源最短路的算法
思考
之前提到过,Dijkstra 不能用于负权图 (怎么又有这句话)
因此我们需要对原图上的边进行预处理,确保所有边的边权均非负.
一种容易想的方法是同时加上 \(x\) ,从而使所有边权非负
肯定是错误的不然为什么大家不用
自己随便构造一组数据就能Hack掉
-
新建一个虚拟结点 \(v_0\),从这个结点到其他所有结点的边的权重为\(0\)。
目的:构造一个能从 \(v_0\)到达所有结点的图,方便跑一次 Bellman-Ford 得到势能函数 $ h(v)$。
-
使用 Bellman-Ford算法求出 \(0\) 到其他所有结点的最短路径权重,记为 \(h(v)\)。
数学性质:对于原图任意边 $(u,v) \in E $,由于 \(dis[v] = min(dis[v], dis[u] + w(u, v))\)(三角形不等式),可得
$h(u) + w(u,v) - h(v) \ge 0 $。
这就是后面重赋权的依据。 -
对每条边 \(e=(u,v)∈E\),重新赋值权重 \(w'=w+ℎ(u)−ℎ(v)\)。
对原图每条边 ( e = (u, v) ),定义新权重:\(w'(u, v) = w(u, v) + h(u) - h(v)\)
-
使用新的\(w'\)权重,每个结点运行
Dijkstra算法,得到最终结果。修正:\(\delta(s, t) = \delta'(s, t) - h(s) + h(t)\)
证明
在讨论 Johnson 算法中边权重标定的正确性之前,我们先回顾一个物理概念——势能。
他和下文有大关系:
1. 边权重标定的定义
对原图 \(G=(V,E)\) 的每条边 \((u,v) \in E\),设其原边权为 \(w(u,v)\)。我们引入一组势能值 \(h_i\)(称为点 \(i\) 的势能),然后定义新边权:
2. 路径长度的变化
考虑新图中一条路径:
其长度在新权重下为:
化简后得到:
3. 路径相对长度不变性
记原图上该路径的长度为:
则新图上该路径的长度为:
由于 \(h_s - h_t\) 是一个只与起点 \(s\) 和终点 \(t\) 有关的常数,原图中任意两条 \(s \to t\) 路径的长度大小关系在新图中完全保持不变。
因此,原图的最短路径必然也是新图的最短路径(反之亦然)。
4. 非负权重的保证
Johnson 算法的关键一步是:通过一次 Bellman–Ford 算法,求出满足如下三角形不等式的势能函数:
由该不等式可得:
因此新图的所有边权 \(w'\) 均为非负值,可以在其上安全运行 Dijkstra 算法。
比较
| 最短路算法 | Floyd | Bellman–Ford | Dijkstra | Johnson |
|---|---|---|---|---|
| 最短路类型 | 每对结点之间的最短路 | 单源最短路 | 单源最短路 | 每对结点之间的最短路 |
| 作用于 | 任意图 | 任意图 | 非负权图 | 任意图 |
| 能否检测负环? | 能 | 能 | 不能 | 能 |
| 时间复杂度 | \(O(n ^ 3)\) | \(O(NM)\) | \(O(M \log M)\) | \(O(NM \log M)\) |
输出
在更新时如q.push()时用一个数组存下来就行啦ヾ(≧▽≦*)o
参考
完结撒花!!!!!

点出发不能抵达一个负环,而不能说明图上不存在负环.
浙公网安备 33010602011771号