Bellman_Ford
基本上用不到的算法,和高精度一样,不常用,用到了又无可代替
常用于限制边数的最短路算法。
使用范围
可以处理任意边权的图,可以处理负环,可以判断负环。
时间复杂度 \(O(nm)\)。因为太慢了,在求最短路的时候基本用不到,但是它的优化版SPFA则大大优化了时间复杂度,算是最短路里最好用的算法,有很强的适应性。但近年来容易被卡。
中心思想
对每个点进行\(n - 1\)轮松弛
每一轮会遍历所有边,一共 \(n - 1\) 轮,时间复杂度也就是 \(O(nm)\)
正确性证明
第 \(1\) 轮松弛得到的是起点到其他点,最多经过 \(1\) 条边的最短路径;第 \(n\) 轮得到的就是从起点到其他点,最多经过 \(n\) 条边点的最短路径。因为在 \(n\) 个点中,两个点之间的最短路径最多有 \(n - 1\) 条边,即对每个点最多进行 \(n - 1\) 轮松弛可以得到最短距离。
如果超过 \(n-1\) 轮还有边可以被松弛,那么说明有源点 \(S\) 点能到达的负环。
实操步骤
- 枚举每个点 \(u\)
- 枚举\(u\)连接着的边 \(w\) 和点 \(j\),并用和 \(dist[u]\) 和 \(w\) 更新 \(dist[j]\)
- 循环上面过程 \(n - 1\) 次
代码
代码和思想一样
但略有不同。
int bellman_ford(int S, int T)
{
memset(dist, 0x3f, sizeof dist);
dist[S] = 0;
int k = n;
while ( -- k)
{
memcpy(g, dist, sizeof dist);
for (int u = 1; u <= n; u ++ )
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
dist[j] = min(dist[j], g[u] + w[i]);
}
}
return dist[T];
}
可以发现代码多了一个\(g\)数组,\(g\)是\(dist\)的复制数组,如果只是求最短路的话可以不用\(g\)代替\(dist\),但如果考虑限制经过\(k\)条边的话,必须加上。设当前为第\(k\)轮,那么\(g\)里面存的就是第\(k - 1\)轮的最短路,如果不这么做直接用\(dist\)更新,可能会出现连续更新的现象,即在这轮用刚更新完的\(dist\)去更新了其他点的\(dist\),就可能会打破边数的限制,在第k轮有经过\(k + n\)条边的最短路径,导致算法错误。因此,有必要加上这个\(g\)数组。
扩展
- 判断负环:据上面所说,我们最多 \(n-1\) 轮就能得出最短路径,也就说第 \(n\) 轮的时候不可能有边还能被松弛,如果有,则说明具有负环,可以无限松弛边长。这里还要注意一点,如果是发现第 \(n\) 轮没有边被松弛,不一定图上没有负环,可能只是源点 \(S\) 到不了而已,因此判断负环时应使用一个超级源点把所有点都连起来,边权为 \(0\) ,这样才能完美地找出负环。