文章目录
贝尔曼-福特算法(Bellman-Ford)
简介
贝尔曼-福特算法(Bellman-Ford)是由理查德·贝尔曼(Richard Bellman) 和 莱斯特·福特 创立的,求解含负权边的带权有向图的单源最短路径问题的一种算法。它的原理是对图进行 V − 1 V-1 V−1次松弛操作( V V V代表图中的点数),从源点逐次绕过其他顶点,以缩短到达终点的最短路径长度,得到不超过 V − 1 V-1 V−1条边构成的最短路径。其优于迪科斯彻算法(Dijkstra)的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高,高达 O ( V E ) O(VE) O(VE)( E E E代表边数)。不能处理带负权边的无向图
Bellman-Ford算法的限制条件为:
- 图中不能包含权值总和为负值的回路(负权值回路)
Bell-Ford算法专用处理可能存在负环的有限路线单源最短路问题。
算法思想
Bellman-Ford算法构造一个最短路径长度数组序列 d i s t 1 [ u ] , d i s t 2 [ u ] , ⋯ , d i s t n − 1 [ u ] dist^1[u],dist^2[u],\cdots,dist^{n-1}[u] dist1[u],dist2[u],⋯,distn−1[u]。其中:
- d i s t 1 [ u ] dist^1[u] dist1[u]为从源点 v v v到终点 u u u的只经过一条边的最短路径长度,并有 d i s t 1 [ u ] = e d g e [ v ] [ u ] dist^1[u]=edge[v][u] dist1[u]=edge[v][u]
- d i s t 2 [ u ] dist^2[u] dist2[u]为从源点 v v v最多经过两条边到达终点 u u u的最短路径长度
- d i s t 3 [ u ] dist^3[u] dist3[u]为从源点 v v v出发最多经过不构成负权值回路的三条边到达终点 u u u的最短路径长度
- ⋯ \cdots ⋯
- d i s t n − 1 [ u ] dist^{n-1}[u] distn−1[u]为从源点 v v v出发最多经过不构成负权值回路的 n − 1 n-1 n−1条边到达终点 u u u的最短路径长度
算法的最终目的是计算出 d i s t n − 1 [ u ] dist^{n-1}[u] distn−1[u],为源点 v v v到顶点 u u u的最短路径长度
算法执行过程
a. 初始化操作,源点 s s s到自己的距离为 0 0 0,源点到其他点的距离为正 ∞ \infty ∞
b. 第一次迭代,找到不超过1条边的最短路
c. 第二次迭代,找到不超过2条边的最短路
d. 第三次迭代,找到不超过3条边的最短路
e. 第四次迭代,找到不超过4条边的最短路
以此类推,可迭代次数是无数次的,但是如果不存在负环,那么当迭代次数与边数相等时就保证了结果。
很重要的一点是每次迭代都是在上一次的基础上进行的,因此在代码实现时要注意保留上一次的结果,在上一次的基础上计算。
应用
题目描述
给定一个 n n n个点 m m m条边的有向图,图中可能存在重边和自环,边权可能为负数。
请你求出从 1 1 1号点到 n n n号点的最多经过 k k k条边的最短距离,如果无法从 1 1 1号点走到 n n n号点,输出 i m p o s s i b l e impossible impossible。
注意:图中可能存在负权回路。
输入格式
第一行包含三个整数 n , m , k n,m,k n,m,k。
接下来 m m m行,每行包含三个整数 x , y , z x,y,z x,y,z,表示存在一条从点 x x x到点 y y y的有向边,边长为 z z z。
输出格式
输出一个整数,表示从 1 1 1号点到 n n n号点的最多经过 k k k条边的最短距离。
如果不存在满足条件的路径,则输出“impossible”。
数据范围
1 ≤ n , k ≤ 500 1≤n,k≤500 1≤n,k≤500,
1 ≤ m ≤ 10000 1≤m≤10000 1≤m≤10000,
任意边长的绝对值不超过 10000 10000 10000。
输入样例:
3 3 1
1 2 1
2 3 1
1 3 3
输出样例:
3
分析
因为存在可能存在负权边,所以Dijkstra算法无法完成。又因为可能存在负环,SPFA算法也无法完成。按道理来说,只要有负环且不限路径数,答案就可能是负无穷,Bellman-Ford算法也不能完成。但是本题给定了路径数k的限制,所以可以用Bellman-Ford算法来实现。
代码
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int k = sc.nextInt();
// 邻接矩阵
int[][] g = new int[n][n];
final int INF = Integer.MAX_VALUE / 2;
// 初始化
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
g[i][j] = (i == j) ? 0 : INF;
}
}
// 存图
for (int i = 0; i < m; i++) {
int x = sc.nextInt() - 1;
int y = sc.nextInt() - 1;
int z = sc.nextInt();
g[x][y] = z;
}
// dist[x] = y 代表从「源点/起点」到 x 的最短距离为 y
int[] dist = new int[n];
// 初始化源点到自己的距离为0,源点到其他点的距离为正无穷
Arrays.fill(dist, INF);
dist[0] = 0;
// 迭代k次
for (int limt = 0; limt < k; limt++) {
int[] clone = dist.clone();
// 更新最短距离
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
dist[j] = Math.min(dist[j], clone[i] + g[i][j]);
}
}
}
if (dist[n - 1] >= INF) {
System.out.println("impossible");
}
System.out.println(dist[n - 1]);
}
}
- 时间复杂度为 O ( n m ) O(nm) O(nm)
LeetCode 787. K 站中转内最便宜的航班
题目描述
Bellman Ford + 邻接矩阵
本题中「限制最多经过不超过 k k k 个点」等价于「限制最多不超过 k + 1 k + 1 k+1 条边」,因此可以使用 Bellman Ford 来求解。
class Solution {
public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
final int INF = Integer.MAX_VALUE / 2;
int[][] g = new int[n][n];
// 邻接矩阵初始化
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
g[i][j] = i == j ? 0 : INF;
}
}
for (int[] f : flights) {
g[f[0]][f[1]] = f[2];
}
// dist[x] = y 代表从「源点/起点」到 x 的最短距离为 y
int[] dist = new int[n];
// 初始化源点到自己的距离为0,源点到其他点的距离为正无穷
Arrays.fill(dist, INF);
dist[src] = 0;
// 迭代k + 1次
for (int limit = 0; limit < k + 1; limit++) {
// 复制上一次的迭代结果
int[] clone = dist.clone();
// 更新最短距离
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
dist[j] = Math.min(dist[j], clone[i] + g[i][j]);
}
}
}
return dist[dst] >= INF ? -1 : dist[dst];
}
}
- 时间复杂度: O ( k ∗ n 2 ) O(k * n^2) O(k∗n2)
- 空间复杂度: O ( n 2 ) O(n^2) O(n2)