BellmanFord算法用于计算单源多节点最短路问题,并且能够处理负权重的边和判断是否存在负环。
在计算每个节点v来说,我们维持一个属性v.d为源节点s到v节点最短距离的上限。称为s到v的最短路径估计,松弛操作就是减少每个节点的v.d值,也就是最短路径的上限。
void relax(int u, int v, int w)
{
if (d[v] > d[u] + w)
{
d[v] = d[u] + w;
}
}
根据上面的代码我们可以知道,松弛一条边(u,v)就是如果存在到节点v更短的路径(s,u)+(u,v),那么就更新v.d的值,很好理解。
该算法的第一步就是初始化d值,把所有节点的d值初始化为无穷大,开始默认都无法达到,把源节点的d值初始化为0,默认开始可以达到而不需要花费权值。
for (int i = 0; i < n; i++)
{
d[i] = INT32_MAX;
}
d[s] = 0;
接下来,我们对边进行松弛。
for (int i = 0; i < n - 1; i++)
{
for (edge e : edges)
{
relax(e.u, e.v, e.w);
}
}
我们外层循环 ∣ V ∣ − 1 |V|-1 ∣V∣−1次,每次循环对所有的边进行松弛操作。即可得到单源多节点最短路数组d。
所有代码如下:
unsigned int d[10001];
struct edge
{
int u;
int v;
int w;
};
void relax(int u, int v, int w)
{
if (d[v] > d[u] + w)
{
d[v] = d[u] + w;
}
}
void bellmanFord(int n, int s, vector<edge> &edges)
{
for (int i = 0; i < n; i++)
{
d[i] = INT32_MAX;
}
d[s] = 0;
for (int i = 0; i < n - 1; i++)
{
for (edge e : edges)
{
relax(e.u, e.v, e.w);
}
}
}
假设从源节点s到某一个目标节点v,存在路径 ( v 0 , v 1 , … , v k ) (v_{0},v_{1},\dots,v_{k}) (v0,v1,…,vk),其中 v 0 v_{0} v0为s, v k v_{k} vk为v,因为最短路都是简单路径,不存在环,因此这个路径最多包含图的所有节点,因此最多包含 ∣ V ∣ − 1 |V|-1 ∣V∣−1条边。在第一次对边的松弛操作之后,边 ( v 0 , v 1 ) (v_{0},v_{1}) (v0,v1)必定会被松弛,因此 v 1 v_{1} v1的d值就是最短路,依次类推第二次松弛必定会松弛边 ( v 1 , v 2 ) (v_{1},v_{2}) (v1,v2), v 2 v_{2} v2更新完毕,最后在第 ∣ V ∣ − 1 |V|-1 ∣V∣−1次松弛之后,边 ( v k − 1 , v k ) (v_{k-1},v_{k}) (vk−1,vk)松弛, v k v_{k} vk的值更新完毕,因此最长的路径需要 ∣ V ∣ − 1 |V|-1 ∣V∣−1次松弛,所以有最外层的循环次数是 ∣ V ∣ − 1 |V|-1 ∣V∣−1。
算法复杂度是 Θ ( V E ) \Theta(VE) Θ(VE)。
void bellmanFord(int n, int s, vector<edge> &edges)
{
for (int i = 0; i < n; i++)
{
d[i] = INT32_MAX;
}
d[s] = 0;
for (int i = 0; i < n - 1; i++)
{
for (edge e : edges)
{
relax(e.u, e.v, e.w);
}
}
for (edge e : edges)
{
if(d[e.v] > d[e.u] + e.w)
{
return false;
}
}
return true;
}
最后我们对每个边进行判断,如果不存在负环,那么根据三角不等式,没有任何的边可以使判别式为真从而返回false,当且仅当存在负环时候才会返回false。
证明可以参阅算法导论中的反证法。
我们可以看到,回看上面的证明,在第一次松弛操作中,如果从边 ( v k − 1 , v k ) (v_{k-1},v_{k}) (vk−1,vk)开始倒着松弛,那么只有最后一次 ( v 0 , v 1 ) (v_{0},v_{1}) (v0,v1)的松弛是有效的,其余都是无效操作,因此我们就要尽量减少这种无效操作,因此就有了队列优化:SPFA。