某快递公司在城市里有 n 个网点,网点之间有一些道路,每条路都有一个"通行耗时"。现在要从 1 号总站出发,把包裹送到所有网点。求到每个网点的最短耗时。
实习生小王的第一版方案很笨但很稳:他先假设"到所有网点都要花无穷久",然后反复检查每一条路 —— 如果"从总站到 u 的最快时间 + 这条路 u→v 的耗时"比现在记录的"到 v 的最快时间"还短,就把记录改小。他一遍又一遍地扫,直到某一轮"一条记录都没变",说明全部最短路都已经确定。这个方法叫 Bellman-Ford:最多扫 n−1 轮,简单、可靠、还能发现负权环。
实习生小李觉得小王太浪费时间:每轮都在重扫那些"根本没变化"的路。她改用一个队列,记下"哪些网点的记录刚刚被改小了",只把这些网点相关的路拿出来重查。这就是 SPFA(队列优化的 Bellman-Ford)—— 平均很快,但在某些特意构造的图上会被反复"折腾"。
正式员工老张的做法最聪明:他先建一个"待定表",把"当前已知最快时间"最小的那个网点取出来,宣布它的最短路已经定稿、不再修改,然后用它去更新邻居。因为所有道路耗时都非负,所以"当前最小的那个值"不可能再被别的方式变小 —— 这就是 Dijkstra 算法。配上优先队列后,复杂度是 O(m log n),是绝大多数题目的首选。
三种算法的动作都是同一句:dis[v] = min(dis[v], dis[u] + w)。
💡 故事背后的算法思想
最短路问题的关键是"松弛(relax)":不断用"已经更优的路径"去修正"还没那么优的估计值"。区别只在于什么时候用哪条边去松弛。
Bellman-Ford 按"轮数"松弛(无脑全扫),SPFA 按"被更新的点"松弛(队列驱动),Dijkstra 按"距离递增顺序"松弛(优先队列驱动)。
三者的正确性前提不同:Bellman-Ford / SPFA 能处理负权但不允许负环;Dijkstra 要求所有边权非负。记住这句话,就不会选错算法。