最短路径:Dijkstra、Bellman-Ford 与 Floyd

图算法学到这里,一个最自然、也最现实的问题就会出现:

从一个点到另一个点,怎么走代价最小?

这里的“代价”可以是很多东西:

  • 距离最短;
  • 时间最少;
  • 花费最低;
  • 风险最小;
  • 路由跳数最少。

这就是最短路径问题。它之所以重要,是因为它几乎贯穿了所有网络、导航、调度和路径规划场景。但“最短路径”并不是只有一种算法,因为图的条件差别非常大:

  • 权值是否都非负;
  • 只求单源,还是求全源;
  • 图规模大不大;
  • 是否存在负权边。

也正因为此,Dijkstra、Bellman-Ford 和 Floyd 不是互相替代的关系,而是分别适用于不同条件的三类经典解法。

一、什么是最短路径问题

最短路径问题通常分成几类:

1. 单源最短路径

给定一个源点,求它到其他所有顶点的最短路径。

2. 单点对最短路径

只关心某个起点到某个终点的最短路。

3. 全源最短路径

要求任意两个顶点之间的最短路径。

不同算法的重点往往就体现在:

  • 是求一源到多点;
  • 还是求所有点对;
  • 是不是允许负权边。

二、为什么最短路径不等于 BFS

前面说过,BFS 可以求无权图最短路,或者每条边权值都相同的情况。但一旦边权不同:

  • “边数最少”不等于“总代价最小”;
  • 少走一步未必更便宜;
  • 这时就需要更一般的最短路径算法。

所以可以这样记:

  • 无权图最短路,优先想 BFS;
  • 带权图最短路,则要看权值性质选择算法。

三、Dijkstra 解决什么问题

Dijkstra 最经典的适用条件是:

求单源最短路径,且所有边权非负。

它的核心思想是贪心:

  • 每次从尚未确定的顶点中,选出当前距离源点最近的那个;
  • 一旦这个点被确定,它的最短距离就不会再被改写;
  • 然后用它去尝试松弛相邻顶点。

这里“松弛”可以理解成:

  • 看看通过当前点走过去,是否能让邻居的距离更短。

四、为什么 Dijkstra 要求边权非负

因为它的贪心成立依赖一个关键前提:

  • 当前最小的暂定距离,以后不会再被负权边绕回来改小。

如果存在负权边,那么:

  • 你今天看起来最短的路径;
  • 明天可能通过一条负边被进一步缩短;
  • 于是“先确定当前最小”这个贪心就失效了。

所以 Dijkstra 的适用边界一定要记牢:

有负权边时,不能直接用它。

五、Dijkstra 为什么常和优先队列一起出现

因为它需要反复做这样一件事:

  • 从所有尚未处理的候选顶点中,快速找出当前距离最小的那个。

这和最小堆 / 优先队列的能力高度一致。于是高效实现通常是:

  • 用优先队列维护候选点;
  • 每次弹出当前最小距离顶点;
  • 再做松弛更新。

所以前面学堆和优先队列,在最短路径这里再次成为核心工具。

六、什么是 Bellman-Ford

Bellman-Ford 也是单源最短路径算法,但它的适用范围更广:

它允许图中存在负权边,并且还能检测负权环。

它的核心思想不是贪心,而是动态地反复松弛所有边:

  • 如果一条最短路径最多经过 k 条边;
  • 那么不断用边去更新距离,最终就能传播出最优值。

更具体地说:

  • 对所有边重复做松弛操作;
  • 最多做 |V| - 1 轮;
  • 因为一个简单路径最多经过 |V| - 1 条边。

七、Bellman-Ford 为什么能处理负权边

因为它不依赖“当前最近点已经最终确定”这种贪心假设,而是更保守地不断传播距离更新。

它允许这样一种情况:

  • 某个点今天还不是最优;
  • 但过几轮之后,通过某条负边路径被改写得更短;
  • 算法仍然能够接收并传播这个变化。

所以相比 Dijkstra,它更慢,但更通用。

八、Bellman-Ford 为什么能检测负权环

如果一张图存在从源点可达的负权环,那么:

  • 你可以在这个环上绕很多圈;
  • 总路径代价会越来越小;
  • 最短路径就不再有意义。

Bellman-Ford 检测的方法通常是:

  • 在做完 |V| - 1 轮松弛后;
  • 再额外检查一轮;
  • 如果还有边能继续被松弛;
  • 就说明存在负权环。

这也是它非常重要的一个特点。

九、什么是 Floyd 算法

Floyd,通常指 Floyd-Warshall 算法,它解决的是:

全源最短路径问题,也就是任意两点之间的最短路径。

它的思路和前两者都不同,更像动态规划。

核心思想是:

  • 逐步考虑“允许哪些中间点参与路径”;
  • 假设当前允许经过前 k 个点;
  • 那么 ij 的最短路,要么不经过 k,要么经过 k
  • 于是就能写成状态转移。

这是一个非常经典的三重循环动态规划模型。

十、Floyd 为什么适合全源最短路径

因为它直接维护一个二维距离矩阵:

  • dist[i][j] 表示 ij 的当前最短距离;
  • 再通过逐步引入中转点不断更新。

最终结果就是所有点对之间的最短路径。

它的优点是:

  • 思路统一;
  • 代码相对简洁;
  • 对负权边也能处理;
  • 还能顺手做可达性闭包等变形。

但缺点也明显:

  • 时间复杂度是 O(n^3)
  • 空间复杂度通常是 O(n^2)
  • 图特别大时不太适合。

十一、这三种算法该怎么选

可以先抓住最重要的决策点。

1. Dijkstra

适合:

  • 单源最短路;
  • 边权非负;
  • 希望高效处理较大图。
2. Bellman-Ford

适合:

  • 单源最短路;
  • 图中可能有负权边;
  • 还希望检测负权环。
3. Floyd

适合:

  • 全源最短路;
  • 图规模不算太大;
  • 想一次性得到任意两点最短距离。

如果只记一句话,可以记成:

  • 非负单源用 Dijkstra;
  • 负权单源用 Bellman-Ford;
  • 全点对用 Floyd。

十二、最短路径和最小生成树有什么区别

这两类问题很容易一起出现,但目标完全不同。

1. 最短路径

优化的是:

  • 某个起点到终点的路径代价;
  • 或所有点对之间的路径代价。
2. 最小生成树

优化的是:

  • 把整张图连起来的总代价最小。

所以:

  • 最短路径关注“走一条路最省”;
  • 最小生成树关注“整体连网最省”。

不能混用。

十三、学习最短路径时最容易踩的坑

1. 有负权边还用 Dijkstra

这是最典型错误之一。

2. 把 BFS 当成所有最短路通解

BFS 只适合无权图或等权边。

3. 不区分单源和全源问题

这会直接影响算法选择。

4. 把最短路径和最小生成树混淆

二者优化目标完全不同。

总结

最短路径之所以重要,不只是因为它常见,而是因为它几乎是图优化问题里最基础的一类模型。真正值得先建立起来的,是这些核心认识:

  • 最短路径问题要先分清:单源、单点对,还是全源;
  • BFS 适合无权图,Dijkstra 适合非负权单源最短路;
  • Bellman-Ford 适合有负权边并支持检测负权环;
  • Floyd 适合规模较小图上的全源最短路;
  • 选择算法的关键,不是死记名字,而是看权值条件、问题范围和图规模;
  • 真正学会最短路径,不是会背三段代码,而是能先判断题目到底属于哪一类最短路模型。

把这一篇理解透之后,后面再去学关键路径和更复杂的图优化问题时,你会更清楚:不同算法虽然都在图上“算路径”,但它们背后优化的对象、前提和使用边界并不相同。

参考资源