【学习笔记】最短路 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\)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的流程如下:
    1. 初始化\(dis[start] = 0\),其余节点的\(dis\)值为无穷大.
    1. 找一个\(dis\)值最小的蓝点\(x\),把节点\(x\)变成白点.
    1. 遍历x的所有出边\((x,y,z)\),若\(dis[y]>dis[x]+z\),则令\(dis[y]=dis[x]+z\)
    1. 重复2,3两步,直到所有点都成为白点.
  • 时间复杂度为\(O(n^2)\)

这就是红黑点染色思想

负权图于Dijkstra

之前提到过,Dijkstra 不能用于负权图

为什么不能处理负权图?

我们观察Dijkstra的思想:

  1. 维护一个 “已确定最短路径的节点集合”
  2. 每次从这个集合的邻接点中,挑选一个距离起点路径最短的节点加入集合,并认为这个距离就是它的最终最短距离

关键在于:一旦一个节点被标记为“已确定”(从优先队列中弹出),算法就不会再回头去更新它。因为它基于一个假设:从已确定集合出发,到任何未确定节点的距离,不会比当前已找到的路径更短

负权边意味着“走得更远”可能反而“代价更小”。这彻底破坏了上述假设。

我们还有一种思考:

  • 正因为图只有正边权时贪心才能生效
  • 否则就有可能WA

Dijkstra算法正确性证明

(接下来我会对每一个难懂的话做解释)

命题:

图 G 的所有边权非负,源点为 \(s\)

算法结束时,对所有节点 \(v\),有 \(dist[v] = d(s, v)\)

先看这个:

在每一步中,从 \(V \ S\) 中选择 \(dist\) 值最小的节点 \(u\),必有 \(dist[u] = d(s, u)\)

  1. 假设存在一条从 \(s\)\(u\) 的真正最短路径 \(P\),使得 \(len(P) = d(s, u) < dist[u]\)

    有一条路径比这条更短

  2. 路径 P 从 s ∈ S 出发,终点是 \(u ∈ V / S\),因此路径上至少有一个点在离开 \(S\) 的时刻。

    你走的所有路径中肯定有一条不在现在知道的路径中的\(S\)

  3. 将 P 分解为:s → ... → x → y → ... → u

    1. 由于边权非负,所以有:

    2.  d(s, y) ≤ d(s, u) < dist[u]
      
  4. 根据算法:

    • 因为 \(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]
      
  5. 串联不等式:

    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掉

  1. 新建一个虚拟结点 \(v_0\),从这个结点到其他所有结点的边的权重为\(0\)

    目的:构造一个能从 \(v_0\)到达所有结点的图,方便跑一次 Bellman-Ford 得到势能函数 $ h(v)$。

  2. 使用 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 $。
    这就是后面重赋权的依据。

  3. 对每条边 \(e=(u,v)∈E\),重新赋值权重 \(w'=w+ℎ(u)−ℎ(v)\)

    对原图每条边 ( e = (u, v) ),定义新权重:\(w'(u, v) = w(u, v) + h(u) - h(v)\)

  4. 使用新的\(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\) 的势能),然后定义新边权:

\[w'(u,v) = w(u,v) + h_u - h_v. \]


2. 路径长度的变化

考虑新图中一条路径:

\[s \to p_1 \to p_2 \to \dots \to p_k \to t. \]

其长度在新权重下为:

\[\begin{aligned} & [w(s,p_1) + h_s - h_{p_1}] \\ +& [w(p_1,p_2) + h_{p_1} - h_{p_2}] \\ +& \cdots \\ +& [w(p_k,t) + h_{p_k} - h_t]. \end{aligned} \]

化简后得到:

\[w(s,p_1) + w(p_1,p_2) + \dots + w(p_k,t) + h_s - h_t. \]


3. 路径相对长度不变性

记原图上该路径的长度为:

\[L_{\text{原}} = w(s,p_1) + w(p_1,p_2) + \dots + w(p_k,t). \]

则新图上该路径的长度为:

\[L_{\text{新}} = L_{\text{原}} + h_s - h_t. \]

由于 \(h_s - h_t\) 是一个只与起点 \(s\) 和终点 \(t\) 有关的常数,原图中任意两条 \(s \to t\) 路径的长度大小关系在新图中完全保持不变
因此,原图的最短路径必然也是新图的最短路径(反之亦然)。


4. 非负权重的保证

Johnson 算法的关键一步是:通过一次 Bellman–Ford 算法,求出满足如下三角形不等式的势能函数:

\[h_v \leq h_u + w(u,v), \quad \forall (u,v) \in E. \]

由该不等式可得:

\[w'(u,v) = w(u,v) + h_u - h_v \geq 0. \]

因此新图的所有边权 \(w'\) 均为非负值,可以在其上安全运行 Dijkstra 算法

比较

最短路算法 Floyd Bellman–Ford Dijkstra Johnson
最短路类型 每对结点之间的最短路 单源最短路 单源最短路 每对结点之间的最短路
作用于 任意图 任意图 非负权图 任意图
能否检测负环? 不能
时间复杂度 \(O(n ^ 3)\) \(O(NM)\) \(O(M \log M)\) \(O(NM \log M)\)

输出

在更新时如q.push()时用一个数组存下来就行啦ヾ(≧▽≦*)o

参考

完结撒花!!!!!

posted @ 2026-02-05 18:15  ExAll  阅读(138)  评论(0)    收藏  举报