图论算法(三)--最短路径 的Bellman-Flod [ 带负权值图 ] 的解法(JAVA )

Bellman-Flod算法

对于带有负权值的图,我们已经不能通过Dijkstra算法进行求解了
原因:Dijkstra每次都会找一个距源点(设为s)最近的点,然后将该距离定为这个点到源点的最短路径;如果一个顶点u被加入了book[i])( book[i] == 1 说明这个s到u的路径权值已被记录,不可再更改),但是如果存在v到u的权值为负,那么s经v到u到值要更小。
例如:
Dijkstra
如果用Dijkstra跑只能得到2这个结果,但实际结果应该是1才对
s—->u 2
s—->v—-> 1


为解决带负权值的问题,这里需要用到Bellman-Flod算法
问题描述:有如下带负权值的图,求从1号点到其他各点的最短路径长度

city
Input:
5 5
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3

Output:
0 -3 -1 2 4

import java.util.Scanner;
public class minPath {
    static int[] u = new int[10];
    static int[] v = new int[10];
    static int[] w = new int[10];
    static int[] dis = new int[10];
    static int n, m;
    static Scanner input  = new Scanner(System.in);

    public static void main(String[] args) {
        n = input.nextInt();
        m = input.nextInt();
        for (int i = 1; i <= m; i++) {
            u[i] = input.nextInt();
            v[i] = input.nextInt();
            w[i] = input.nextInt();
        }
        for (int i = 1; i <= n; i++) {
            dis[i] = 99999999;
        }
        dis[1] = 0;
        bellmanFlod();
        for (int i = 1; i <= n; i++) {
            System.out.print(dis[i] + " ");
        }
    }

    private static void bellmanFlod() {
        /**
         * 由离散数学的理论得出,在一个n个顶点的图中,任意两点之间的最短路径最多包含n-1条边
         * */
        for (int i = 1; i <= n - 1; i++) {
            for (int j = 1; j <= m; j++) {
                if (dis[v[j]] > dis[u[j]] + w[j]) {
                    dis[v[j]] = dis[u[j]] + w[j];
                }
            }
        }
    }
}

时间复杂度:O(NM)

算法优化

优化版本一

优化原因:在实际操作中,很多时候不会进行n-1次,毕竟不是每个点都会达到n-1条边的情况的,因此当不在能更新之后,我们就可以终止程序了,即使现在还没进行到第n-1阶段。

优化方法:添加一个一维数组备份来dis,如果新一轮的松弛中数组没有发生变化,则可以提前跳出循环。

import java.util.Scanner;

public class minPath {
    static int[] u = new int[10];
    static int[] v = new int[10];
    static int[] w = new int[10];
    static int[] dis = new int[10];
    static int[] bak = new int[10];
    static int n, m;
    static int check, flag;
    static Scanner input  = new Scanner(System.in);

    public static void main(String[] args) {
        n = input.nextInt();
        m = input.nextInt();
        for (int i = 1; i <= m; i++) {
            u[i] = input.nextInt();
            v[i] = input.nextInt();
            w[i] = input.nextInt();
        }
        for (int i = 1; i <= n; i++) {
            dis[i] = 99999999;
        }
        dis[1] = 0;

        bellmanFlod();

        if (flag == 1) {
            System.out.println("此图含有负权回路");
        } else {
            for (int j = 1; j <= n; j++) {
                System.out.print(dis[j] + " ");
            }
        }
    }

    private static void bellmanFlod() {
        /**
         * 由离散数学的理论得出,在一个n个顶点的图中,任意两点之间的最短路径最多包含n-1条边
         * */
        for (int i = 1; i <= n - 1; i++) {
            bak[i] = dis[i];
            for (int j = 1; j <= m; j++) {
                if (dis[v[j]] > dis[u[j]] + w[j]) {
                    dis[v[j]] = dis[u[j]] + w[j];
                }
            }
            /**
             * 检测dis是否还在变化
             * */
            check = 0;
            for (int j = 1; j <= n; j++) {
                if (bak[i] != dis[i]) {
                    check = 1;
                    break;
                }
                if (check == 0) {
                    break;
                }
            }
            /**
             * 检测负权回路
             * 原理:最短路径所包含的边做多为n-1条,在进行n-1次松弛之后如果还能继续松弛成功,
             * 那说明此图必定含有负权回路
             * */
            flag = 0;
            for (int j = 1; j <= m; j++) {
                if (dis[v[i]] > dis[u[i]] + w[i]) {
                    flag = 1;
                }
            }
        }
    }
}

优化原因:因为最短路径做多有n-1条边,所以Bellman-Ford算法做多有n-1个阶段。而每个阶段都要对每条边进行“松弛操作”,使得“估计值”变为“确定值”。而对于本阶段来说,上个阶段松弛完的结果是不会再改变的,而原算法每个阶段都会重新计算,浪费时间。

优化方法:每次仅对最短路“估计值”变化为“确定值”的顶点的所有出边执行松弛操作,可以采用队列进行优化
时间复杂度:O(NM)

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class minPath {
    static int[] u = new int[8];
    static int[] v = new int[8];
    static int[] w = new int[8];
    /**
     * first 要比 n 的值大1
     * next  要比 m 的值大1
     * */
    static int[] first = new int[6];
    static int[] next = new int[8];

    static int[] dis = new int[6];
    static int[] book = new int[6];
    static int n, m;
    static Queue<Integer> queue = new LinkedList<>();
    static Scanner input = new Scanner(System.in);

    public static void main(String[] args) {

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

        for (int i = 1; i <= n; i++) {
            dis[i] = 99999999;
        }
        dis[1] = 0;
        for (int i = 1; i <= n; i++) {
            first[i] = -1;
        }

        for (int i = 1; i <= m; i++) {
            u[i] = input.nextInt();
            v[i] = input.nextInt();
            w[i] = input.nextInt();
            /**
             * 建立邻接表的关键语句!!!
             * */
            next[i] = first[u[i]];
            first[u[i]] = i;
        }

        queue.offer(1);
        book[1] = 1;

        bellomanFlodPlus();

        for (int i = 1; i <= n; i++) {
            System.out.print(dis[i] + " ");
        }
    }

    private static void bellomanFlodPlus() {
        while (!queue.isEmpty()) {
            /**
             * 当前的队首元素
             * */
            int k = first[queue.peek()];
            while (k != -1) {

                if (dis[v[k]] > dis[u[k]] + w[k]) {
                    dis[v[k]] = dis[u[k]] + w[k];
                    /**
                     * 用数组来判断v[k]是否已经在队列中
                     * 不使用数组进行记录的话,要迭代队列,十分不方便
                     * */
                    if(book[v[k]] == 0) {
                        queue.offer(v[k]);
                        book[v[k]] = 1;
                    }
                }
                k = next[k];
            }
            book[queue.peek()] = 0;
            queue.remove();
        }
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值