Belloman-Ford算法
算法介绍
Dijkstra可以解决单源无负边最短路径问题。但是当遇到含有负边的单源最短路径问题就需要使用Bellman-Ford算法来解决。Bellman-Ford算法还可以检测出负环。
算法步骤
- 源点s,数组d[u]d[u]d[u]表示s到u的最短距离
- 初始化:d[s]=0d[s]=0d[s]=0,∀v∈V−{s},d[v]=∞\forall v \in V-\{s\},d[v]=\infin∀v∈V−{s},d[v]=∞
- 循环∣V∣−1|V|-1∣V∣−1次,每次对每条边进行松弛操作:对于有向边(u,v)(u,v)(u,v),如果d[u]+w[u,v]<d[v]d[u]+w[u,v]<d[v]d[u]+w[u,v]<d[v],则d[v]=d[u]+w[u,v]d[v]=d[u]+w[u,v]d[v]=d[u]+w[u,v]
- 对每条边进行松弛操作,如果操作成功,说明有负环。否则最后的d[i]d[i]d[i]就是源点s到i的最短路径长度
我们可以看到Bellman-Ford算法的算法结构是比较简单的,复杂度是O(VE)O(VE)O(VE),比Dijkstra算法最好为O(E+VlogV)O(E+VlogV)O(E+VlogV)大,但是可以处理负边。
正确性证明
可以看到算法的核心操作就是松弛。这就要用到另一篇博客中介绍Dijkstra算法的时候的几个关于松弛操作的引理,这里就不再进行证明,如果想要看证明可以移步:传送门。
设源点为s,d[u]表示源点到u的最短路径,w[u,v]表示从点u到点v的一条有向边的长度,δ(u,v)\delta(u,v)δ(u,v)表示u到v的最短路径。
最优子结构:任意两点之间的最短路径的子路径仍然是最短路径
引理1:初始化以后∀v∈V,d[u]⩾δ(s,v)\forall v \in V,d[u]\geqslant\delta(s,v)∀v∈V,d[u]⩾δ(s,v)
引理2:假设存在s到v的最短路径:s->…->u->v,d[u]=δ(s,u)d[u]=\delta(s,u)d[u]=δ(s,u),那么在对边(u,v)进行松弛操作以后d[v]=δ(s,v)d[v]=\delta(s,v)d[v]=δ(s,v)
假设没有负边环,那么:
- 对于初始情况,d[s]=0=δ(s,s)d[s]=0=\delta(s,s)d[s]=0=δ(s,s)
因为如果d[s]<0d[s]<0d[s]<0说明存在一个环s->…->s的权值和为负,而假设没有负边环,所以不存在这样的情况
- 假设对于最短路径经过边数为k的点u在第k次循环d[u]=δ(s,u)d[u]=\delta(s,u)d[u]=δ(s,u),那么由引理2,所有最短路径经过边数为k+1的点v在第k+1次循环后,d[v]=δ(s,v)d[v]=\delta(s,v)d[v]=δ(s,v)
- 因为一个节点数目为∣V∣|V|∣V∣的图的简单路径最长为∣V∣−1|V|-1∣V∣−1,所以∣V∣−1|V|-1∣V∣−1次循环以后∀v∈V,d[v]=δ(s,v)\forall v \in V,d[v]=\delta(s,v)∀v∈V,d[v]=δ(s,v)
- 第|V|次循环应该无法进行松弛操作。
如果第|V|次循环可以进行松弛操作,则由逆反命题,说明有负边环。
证毕。
SPFA算法
算法介绍
SPFA算法是对Bellman-Ford算法的优化。因为Bellman-Ford算法中会对已经确定最短路径的点进行松弛判断,但这实际上是不必要的。我们需要松弛的是被松弛的点可以到达的点。正是这种思想我们有了SPFA算法。
算法步骤
- 源点s,数组d[u]d[u]d[u]表示s到u的最短距离,队列Q表示需要操作进行松弛操作的点的集合
- 初始化:d[s]=0,∀v∈V−{s},d[v]=∞d[s]=0,\forall v \in V-\{s\},d[v]=\infind[s]=0,∀v∈V−{s},d[v]=∞,将点s加入Q中
- 从Q中取出一个点,对它可以到达的点进行松弛操作,并将成功进行松弛操作的点加入Q中
- 重复上述步骤直到Q为空
- 如果某个点被松弛了|V|次,说明有负边环
正确性证明
同Bellman-Ford算法,可以用归纳法进行证明。本质上SPFA算法提取出了Bellman-Ford算法中有效的操作。
复杂度分析
可以构造图使得算法复杂度为O(VE)O(VE)O(VE),因此算法的最坏复杂度为O(VE)O(VE)O(VE)。虽然SPFA算法是Bellman-Ford算法的优化,但是对于一些特殊的图可能因为有判断操作和出队入队操作导致两者复杂度相近。