「这是我参与2022首次更文挑战的第15天,活动详情查看:2022首次更文挑战」。
贝尔曼-福特算法是求解单源最短路径问题的一种算法,由理查德·贝尔曼(Richard Bellman) 和 莱斯特·福特 创立的。有时候这种算法也被称为 Moore-Bellman-Ford 算法,因为 Edward F. Moore 也为这个算法的发展做出了贡献。
它的原理是对图进行|V|-1次松弛操作,得到所有可能的最短路径。优于Dijkstra算法的方面。边的权值可以为负数、实现简单,缺点是时间复杂度过高,高达O(VE)。但算法可以进行若干种优化,提高效率。
【松弛操作】:是指对于每个顶点v∈V,都设置一个属性d[v],用来描述从源点s到v的最短路径上权值的上界。
单源最短路径问题:
给定来自加权有向图中的s一组顶点的源顶点,其中它的边权重可以为负,找到图中所有顶点的最短路径权重。
案例:
让给定的源顶点为 0。将所有距离初始化为无限,除了到源本身的距离。图中的顶点总数为 5,因此所有边必须处理 4 次。
让所有边按以下顺序处理:(B, E), (D, B), (B, D), (A, B), (A, C), (D, C), (B, C ), (E, D)。当第一次处理所有边缘时,我们得到以下距离。第一行显示初始距离。第二行显示处理边缘 (B, E)、(D, B)、(B, D) 和 (A, B) 时的距离。第三行显示处理 (A, C) 时的距离。第四行显示何时处理 (D, C)、(B, C) 和 (E, D)。
第一次迭代保证给出最多 1 条边长的所有最短路径。当第二次处理所有边缘时,我们得到以下距离(最后一行显示最终值)。
第二次迭代保证给出最多 2 条边长的所有最短路径。该算法再处理所有边 2 次。第二次迭代后距离最小化,因此第三次和第四次迭代不会更新距离。
Java实现:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
// 存储图边的类
class Edge
{
int source, dest, weight;
public Edge(int source, int dest, int weight)
{
this.source = source;
this.dest = dest;
this.weight = weight;
}
}
class Main
{
// 递归函数,从源顶点打印给定顶点的路径
static void getPath(int parent[], int vertex, List<Integer> path)
{
if (vertex < 0) {
return;
}
getPath(parent, parent[vertex], path);
path.add(vertex);
}
// 函数从给定源运行Bellman–Ford算法
public static void bellmanFord(List<Edge> edges, int source, int n)
{
// distance[] and parent[] 存储最短路径
// (least cost/path)
int distance[] = new int[n];
int parent[] = new int[n];
// 初始化[]` and `parent[]`. 所有顶点
// 除了源顶点权重无穷大和没有父顶点
Arrays.fill(distance, Integer.MAX_VALUE);
distance[source] = 0;
Arrays.fill(parent, -1);
// 松弛 V-1 次
for (int i = 0; i < n - 1; i++)
{
for (Edge edge: edges)
{
// 从 `u` 到 `v`的边,有权重 `w`
int u = edge.source;
int v = edge.dest;
int w = edge.weight;
if (distance[u] != Integer.MAX_VALUE && distance[u] + w < distance[v])
{
// 将距离更新为新的较小值
distance[v] = distance[u] + w;
// 将v的父对象设置为u
parent[v] = u;
}
}
}
// 检查负权重循环
for (Edge edge: edges)
{
// 从 `u` 到 `v`的边,有权重 `w`
int u = edge.source;
int v = edge.dest;
int w = edge.weight;
if (distance[u] != Integer.MAX_VALUE && distance[u] + w < distance[v])
{
System.out.println("发现负权重循环!!");
return;
}
}
for (int i = 0; i < n; i++)
{
if (i != source && distance[i] < Integer.MAX_VALUE) {
List<Integer> path = new ArrayList<>();
getPath(parent, i, path);
System.out.println("The distance of vertex " + i + " from vertex " +
source + " is " + distance[i] + ". Its path is " + path);
}
}
}
public static void main(String[] args)
{
// 图边列表如上图所示
List<Edge> edges = Arrays.asList(
new Edge(0, 1, -1), new Edge(0, 2, 4), new Edge(1, 2, 3),
new Edge(1, 3, 2), new Edge(1, 4, 2), new Edge(3, 2, 5),
new Edge(3, 1, 1), new Edge(4, 3, -3 )
);
// 设置图中的最大节点数
int n = 5;
// 从每个节点运行Bellman–Ford算法
for (int source = 0; source < n; source++) {
bellmanFord(edges, source, n);
}
}
}
输出: 顶点 1 到顶点 0 的距离为 -1。它的路径是 [0, 1] 顶点 2 到顶点 0 的距离是 2。 它的路径是 [0, 1, 2] 顶点 3 到顶点 0 的距离是 -2。它的路径是[0,1,4,3] 顶点4到顶点0的距离是1它的路径是[0,1,4] 顶点2到顶点1的距离是3它的路径是[1, 2] 顶点3到顶点1的距离为-1。它的路径是 [1, 4, 3] 顶点 4 到顶点 1 的距离是 2 它的路径是 [1, 4] 顶点 1 到顶点 3 的距离是 1 它的路径是 [3, 1] 距离顶点 2 到顶点 3 的距离是 4。它的路径是 [3, 1, 2] 顶点 4 到顶点 3 的距离是 3。它的路径是 [3, 1, 4] 顶点 1 到顶点 4 的距离为 -2。它的路径是[4, 3, 1] 顶点2到顶点4的距离是1。它的路径是[4, 3, 1, 2] 顶点3到顶点4的距离是-3。它的路径是 [4, 3]
Bellman-Ford 算法的时间复杂度为O(V × E),其中V和E分别是图中顶点和边的总数。
参考: