Bellman-Ford
Bellman-Ford 算法各方面都没有 SPFA 算法优良,但是有些题只能用 Bellman-Ford 算法来求,SPFA 算法要求图中不存在负环。不过,只要图中没有负环,都可以使用 SPFA,绝大部分算法题求最短路都没负环。
Bellman-Ford 算法对边的存储不太敏感,你甚至可以不使用邻接表或邻接矩阵存边,直接使用一个结构体一维数组也行。
struct Edge {
int a, b, w
}edges[M];
Bellman-Ford 算法模板
dist[i] 表示从 1 号点到 i 号点的最短距离。
memset(dist, 0x3f, sizeof dist);
dist[1] = 0; // 距离数组初始化操作
for n 次 {
memcpy(backup, dist, sizeof dist); // 需要使用备份数组,防止发生“串联”
for 所有边 a,b,w {
dist[b] = min(dist[b], backup[a] + w); // 使用backup而不是dist,是因为dist[a]可能在第二个for循环中被提前更新了。我们应当使用第一个for循环的上一次迭代得到的dist[a]数组值
}
}
外层 for 循环实际含义:循环到第 k 次时,表示从 1 号点,经过不超过 k 条边,到达所有点的最短距离(这个距离可能存在也可能不存在)。 对于外层 for 循环,每次迭代都会确定 一个 从起点到次点的最短路径。因此想要确定起点到 n 号点且不多于 k 条边的最短路径就是循环 k 次。
对于边 w(a, b),当 d[a] 第一次更新时,d[b] 才可能更新,在 d[a] 被更新之前,d[b] 的值一直是无穷大。 若图中存在未确认的顶点,则对边集合的一次迭代后,会增加至少一个已确认顶点
对于存在负权回路的最短路,答案可能不存在。
const int N = 510, M = 10010;
int n, m, k;
int d[N];
int backup[N];
struct Edge {
int a, b, w;
}edges[M];
int bellman_ford() {
memset(d, 0x3f, sizeof d); // 距离数组初始化为一个极大值
d[1] = 0;
while (k --) {
memcpy(backup, d, sizeof d);
for (int i = 1; i <= m; ++i) {
Edge& now = edges[i];
d[now.b] = min(d[now.b], backup[now.a] + now.w);
}
}
if (d[n] >= 0x3f3f3f3f / 2) return -1;
else return d[n];
}
Bellman-Frod 算法时间复杂度
O ( n m ) O(nm) O(nm),其中 n 表示图的点数,m 表示图的边数。