JAVA实践Bellman-Ford最短路径距离算法

前言

之前尝试过Dijkstra算法,用起来不错,想输出路径特别方便,但是有个问题,它不能解决负权边。
而今天的Bellman-Ford算法,就可以解决此问题。

实现功能

对带有负权的图求出两点之间的最短距离
输出实际路径

中文版参考

/**
 * Bellman-Ford算法,可解负权边
 * 这个算法既有Floyd-Warshall算法的影子,又有Dijkstra算法的味道。
 *
 * 其思想是遍历寻找其他顶点距离首个指定顶点的距离,暂时不能到达的为无穷大。
 * 然后引入了一个中转的边,查看通过这个中转的边能否让两点距离变短。
 * 再进行遍历寻找顶点1到其他顶点的最短距离,因为经过上面的一次遍历,已经有值改变了
 * 此时会找出其他的点的最短路径,更新之
 * 至多重复遍历n-1次即可完成最短路径的寻找
 *
 * 判断中转是否比较短的方法是
 * 起点到目标顶点的距离 = Math.min(起点到目标顶点的[直接]距离, 起点经过其他顶点到达目标顶点的距离)
 *
 * 一般这样描述都很难懂,上实战
 *
 * 例如
 * 起   终  权
 * 2    3   2
 * 1    2   -3
 * 1    5   5
 * 4    5   2
 * 3    4   3
 *
 * 5个顶点
 * 5条边
 *
 * 使用三个一维数组分别存储顶点起点、顶点终点、两个点之间的权值
 * fromVertex toVertex  weightVertex
 * 使用一个dis数组,存储的是第一个起点,到达其他顶点的最短距离
 * 初始为dis{0,    Inf,    Inf,    Inf,    Inf}
 * 处理第一条边
 * 2    3   2
 * 终点顶点是3,查看首个顶点到达顶点3的目前估计最短距离是: dis[toVertex[1]] = dis[3] = Inf
 * 起点顶点时2,查看首个顶点到达顶点2的目前估计最短距离是: dis[fromVertex[1]] = dis[2] = Inf
 * 顶点2到顶点3的权值是2
 *
 * 我们需要查看的是
 *                  A:首个顶点到达顶点3的目前估计最短距离dis[3]
 *                  B:首个顶点到达顶点2的目前估计最短距离dis[2] 加上 顶点2到顶点3的权值2
 * 两种情况
 *      A 大于 B
 *          那么将A的距离更新为B,原因如下:
 *          A表示的是:  顶点1→顶点3
 *          B表示的是: 顶点1→顶点2→顶点3
 *          可以看出,起点和终点都是相同的,唯一不同的是B有个顶点2中转,使得1到达3变短了
 *          所以在起点与重终点不变的情况下,可以更新其距离
 *          即dis[3] = dis[2] + 2
 *      A 不大于 B
 *          那么表示从顶点1直接到达顶点3更短,所以不更新dis[3]的最短距离
 *          进行下一次探查
 * 目前情况:    A 不大于 B
 *
 * 所以开始处理第下一条边
 * 1    2   -3
 * 终点顶点是2,查看首个顶点到达顶点2的目前估计最短距离是: dis[toVertex[2]] = dis[2] = Inf
 * 起点顶点时1,查看首个顶点到达顶点1的目前估计最短距离是: dis[fromVertex[1]] = dis[1] = 0
 * 顶点2到顶点3的权值是-3
 *      A:Inf
 *      B:0 + (-3)
 * 目前情况:    A 大于 B
 *      所以令A = B
 *      dis[2] = -3
 *
 * 处理下一条边
 * 1    5   5
 * 终点顶点是5,dis[toVertex[3]] = dis[5] = Inf
 * 起点顶点时1,dis[fromVertex[3]] = dis[1] = 0
 * 15的权值是5
 *      A:Inf
 *      B:0 + 5
 * 因此   A 大于 B
 *      所以dis[5] = 5
 *      ·
 *      ·
 *      ·
 * 一直到5条边处理完毕,此时的dis是{0 -3  Inf Inf 5}
 *
 * 现在再重复上面的步骤,再对5条边进行一样的步骤
 * 第二次处理第一条边
 * 2    3   2
 * 终点顶点是3,查看首个顶点到达顶点3的目前估计最短距离是: dis[toVertex[1]] = dis[3] = Inf
 * 起点顶点时2,查看首个顶点到达顶点2的目前估计最短距离是: dis[fromVertex[1]] = dis[2] = -3(因为第一次的循环,此处已改变)
 * 顶点2到顶点3的权值是2
 *      A:Inf
 *      B:-3 + 2 = -1
 * 因此   A 大于 B
 *      所以dis[3] = -1
 *      ·
 *      ·
 * 重复至多n-1次即可找出最短路径
 *
 * 不过在dis数组没有变化时,表示已经寻找完毕,可提前退出循环。
 *
 * 以上就是Bellman-Ford求最短路径的步骤
 */

代码实现

import java.util.Scanner;
public class BellmanFord {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        //起点,终点,权值,最短距离
        int[] fromVertex, toVertex, weightVertex, dis, preNode;
        //顶点个数,边
        int n, m;
        //表无穷大
        int Inf = 999;
        n = in.nextInt();
        m = in.nextInt();

        //初始化数据
        fromVertex = new int[m];
        toVertex = new int[m];
        weightVertex = new int[m];
        dis = new int[n];
        preNode = new int[n];

        for (int i = 0; i < m; i++) {
            fromVertex[i] = in.nextInt();
            toVertex[i] = in.nextInt();
            weightVertex[i] = in.nextInt();
        }
        //初始化dis
        dis[0] = 0;
        preNode[0] = 1;
        for (int i = 1; i < n; i++) {
            dis[i] = Inf;
        }

        boolean flag;
        //开始处理边,寻找最短路径
        for (int k = 0; k < n - 1; k++) {
            flag = true;
            for (int i = 0; i < m; i++) {
                if (dis[toVertex[i] - 1] > dis[fromVertex[i] - 1] + weightVertex[i]) {
                    dis[toVertex[i] - 1] = dis[fromVertex[i] - 1] + weightVertex[i];
                    //记录终点顶点的前一个顶点
                    //用于输出路径,目标顶点的前一个点到目标顶点一定最短
                    preNode[toVertex[i] - 1] = fromVertex[i];
                    flag = false;
                    //System.out.println("from " + fromVertex[i] + " to " + toVertex[i] + " i=" + (i+1));
                    //刚开始不明白怎么输出路径,就打印了一下
                    //System.out.println("pre " + toVertex[i] + " from " + fromVertex[i] + " i=" + (i+1));
                }
            }
            //如果没有更新过最短路径,表示最短路径寻找完毕。
            if (flag) {
                break;
            }
        }

        //输出顶点1到各个顶点之间的最短路径和距离
        for (int i = 0; i < 5; i++) {
            showPath(preNode, 1, i+1);
            System.out.println(" 距离:" + dis[i]);
        }
    }
    public static void showPath(int[] path, int from, int to) {
        int pre;
        if (from == to) {
            System.out.print(to + " ");
            return;
        }
        pre = path[to - 1];
        showPath(path, from, pre);
        System.out.print(" " + to + " ");
    }
}

结果

输入
5 5
2 3 2
1 2 -3
1 5 3(顶点1到顶点5的权值被我换成了3)
4 5 2
3 4 3
输出
1  距离:0
1  2  距离:-3
1  2  3  距离:-1
1  2  3  4  距离:2
1  5  距离:3

一点瞎扯

利用递归倒序输出路径的思路,很早以前看过,一直没怎么用。
然后自己尝试着写出来了。

很多之前看过觉得很高端的东西,当时写不出来,后来慢慢能写出来了,非常有成就感。

就像快速排序,我很早就知道它,也是前不久才写出来,对于暂时不懂得,不要心急。

出来混的总是要还的,当时学不会的,总可以试着以后补回来。

但是,别忘了。

END

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