JAVA实践使用队列优化Bellman-Ford最短路径算法

前言

本文内容参考《啊哈,算法》,算是前一个版本的后续。

实现功能

对不带环的图,不论是否存在负权,都可以寻找到其最短路径。
输出实际最短路径。

中文版参考

/**
 * 队列优化Bellman-Ford最短路径算法
 *
 * 假设5个顶点7条边
 * Bellman-Ford最短路径算法的核心代是对7条边进行4次的遍历
 * 它不管你当前是否已经是最短路径
 * 也不管这条边是否真的需要进行判断
 * 都进行判断
 * 所以有O(n*m)的时间复杂度
 *
 * 队列优化的想法是使用队列存储需要判断的顶点,只对需要判断的顶点的边进行判断
 * 假设
             5 7
             1 2 2
             1 5 10
             2 3 3
             2 5 7
             3 4 4
             4 5 5
             5 3 6
 * Bellman-Ford对7条边进行判断,更新路径长度
 * 第二次还是七条边判断,更新路径长度
 *
 * 优化的算法是
 * 使用一个队列queue维护需要判断的顶点
 * 每次只判断该顶点所有的边
 * 过程如下
 * 初始
 *      dis存储顶点1到其他顶点的最短距离
 *      dis{0, Inf, Inf, Inf, Inf},表示顶点1为起点
 *      顶点1入队
 *      标记顶点1已经在队列内 mark[1] = true;
 * 循环开始
 *      读取队列头,发现队列头是顶点1,遍历顶点1所有的边。
 *      1   2   2
 *      判断  dis[2] > dis[1] + 2    成立,所以dis[2] = dis[1] + 2 = 2;(此处乃初版Bellman-Ford的核心)
 *      dis{0, 2, Inf, Inf, Inf}
 *      同时,判断2是否在队列中,当前不在队列中
 *      所以将2入队。因为dis[2]有所改变,说明顶点2到其他路径的最短距离也可以发生改变
 *      同时标记mark[2] = true;
 *
 *      1   5   10
 *      判断  dis[5] > dis[1] + 10    成立,所以dis[5] = dis[1] + 10 = 10;
 *      dis{0, 2, Inf, Inf, 10}
 *      同理,判断5是否在队列中,当前不存在,所以将5入队。
 *      标记mark[5] = true;
 *
 *      现在队列头所有的边已经处理完毕,将其出队。
 *      即顶点1出队,同时去除标记mark[1] = false;
 * 循环结果
 * 再次循环
 *      此时刚刚入队的顶点2成为队列头。
 *      读取队列头,发现队列头是顶点2,遍历顶点2所有的边。
 *      2   3   3
 *      判断  dis[3] > dis[2] + 3    成立,所以dis[3] = dis[2] + 3 = 5;
 *      dis{0, 2, 5, Inf, 10}
 *      同理,判断3是否在队列中,当前不存在,所以将3入队。
 *      标记mark[3] = true;
 *
 *      2   5   7
 *      判断  dis[5] > dis[2] + 7     成立,所以dis[5] = dis[2] + 7 = 9;
 *      dis{0, 2, 5, Inf, 9}
 *      同理,判断5是否在队列中
 *      5已经在队列中,因为mark[5]=true,所以5不需要再次入队
 *
 *      顶点2也处理完毕了,将其出队,同时取消标记
 *      mark[2] = false;
 * 循环结束
 *      此时顶点5就成了队列头了,继续和上面一样的操作
 *      ·
 *      ·
 *      ·
 *      如果存在顶点i发生改变,同时i不在队列中时,就将i入队。
 *      一直到队列为空,即后续没有顶点改变了。
 *      此时最短路径搜索结束。
 */

代码实现

import java.util.Scanner;
public class OptimumBellmanFord {
    static int[] fromVertex, toVertex, weightVertex, first, next, dis, queue, path;
    static boolean[] mark;
    static int Inf, head, tail;

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n, m;//顶点个数,边数

        n = in.nextInt();
        m = in.nextInt();

        init(n, m);

        for (int i = 0; i < m; i++) {
            fromVertex[i] = in.nextInt();
            toVertex[i] = in.nextInt();
            weightVertex[i] = in.nextInt();
            //邻接表存储
            next[i] = first[fromVertex[i] - 1];
            first[fromVertex[i] - 1] = i;
        }

        int k;
        while (head < tail) {
            k = first[queue[head] - 1];
            while (k != -1) {
                if (dis[toVertex[k] - 1] > dis[fromVertex[k] - 1] + weightVertex[k]) {
                    dis[toVertex[k] - 1] = dis[fromVertex[k] - 1] + weightVertex[k];
                    path[toVertex[k] - 1] = fromVertex[k];

                    if (!mark[toVertex[k] - 1]) {
                        queue[tail++] = toVertex[k];
                        mark[toVertex[k] - 1] = true;
                    }
                }
                k = next[k];
            }
            mark[queue[head] - 1] = false;
            head++;
        }
        for (int i = 0; i < n; i++) {
            showPath(path, 1, i + 1);
            System.out.println("距离:" + dis[i]);
        }
    }

    public static void init(int n, int m) {
        Inf = 99;
        queue = new int[n * m];
        head = tail = 0;
        fromVertex = new int[m];
        toVertex = new int[m];
        weightVertex = new int[m];
        first = new int[n];
        next = new int[m];
        //用于检测顶点是否在队列内
        mark = new boolean[n];
        dis = new int[n];
        path = new int[n];

        //当前顶点1入队
        queue[tail++] = 1;
        mark[0] = true;
        //初始化数组
        for (int i = 0; i < n; i++) {
            dis[i] = i * Inf;
            first[i] = -1;
        }
        //初始化next数组
        for (int i = 0; i < m; i++) {
            next[i] = -1;
        }
    }

    public static void showPath(int[] path, int from, int to) {
        if (from == to) {
            System.out.print(to + " ");
            return;
        }
        int pre = path[to - 1];
        showPath(path, from, pre);
        System.out.print(" " + to + " ");
    }
}

结果

输入:
5 7
1 2 2
1 5 10
2 3 3
2 5 7
3 4 4
4 5 5 
5 3 6

输出:
1 距离:0
1  2 距离:2
1  2  3 距离:5
1  2  3  4 距离:9
1  2  5 距离:9

一点收获

再次感受到写中文版参考的好处,之前学的算法没几天会忘掉,今天要再次写,会想起写中文参考时的思路,就能想出来,而不会感觉这玩意很“新鲜”。

END

    原文作者:Bellman - ford算法
    原文地址: https://blog.csdn.net/xubaifu1997/article/details/51996879
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞