一天一个经典算法:贝尔曼-福特算法(春节档)

228 阅读4分钟

「这是我参与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 次。

image.png

让所有边按以下顺序处理:(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)。

image.png

第一次迭代保证给出最多 1 条边长的所有最短路径。当第二次处理所有边缘时,我们得到以下距离(最后一行显示最终值)。

image.png

第二次迭代保证给出最多 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),其中VE分别是图中顶点和边的总数。

参考:

en.wikipedia.org/wiki/Bellma…

ocw.mit.edu/courses/ele…

www.techiedelight.com