代码随想录Day52图:Bellman_ford队列优化算法_Bellman_ford之判断负权回路_ Bellman_ford之单源有限最短路

代码随想录Day52图:Bellman_ford队列优化算法_Bellman_ford之判断负权回路_ Bellman_ford之单源有限最短路

Bellman_ford队列优化算法

题目:某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。
请找出从城市 1 到城市 n 的所有可能路径中,综合政府补贴后的最低运输成本。如果最低运输成本是一个负数,它表示在遵循最优路径的情况下,运输过程中反而能够实现盈利。城市 1 到城市 n 之间可能会出现没有路径的情况,同时保证道路网络中不存在任何负权回路。(负权回路是指一系列道路的总权值为负,这样的回路使得通过反复经过回路中的道路,理论上可以无限地减少总成本或无限地增加总收益。)
输入描述:第一行包含两个正整数,第一个正整数n表示该国一共有n个城市,第二个整数 m表示这些城市中共有 m 条道路。接下来为m行,每行包括三个整数,s、t 和 v,表示 s号城市运输货物到达t号城市,道路权值为v(单向图)。
输出描述:如果能够从城市1到连通到城市n, 请输出一个整数,表示运输成本。如果该整数是负数,则表示实现了盈利。如果从城市1没有路径可达城市n,请输出 “unconnected”。
链接:https://kamacoder.com/problempage.php?pid=1152

Bellman_ford 算法每次松弛都是对所有边进行松弛。但真正有效的松弛,是基于已经计算过的节点在做的松弛。
只需要对上一次松弛的时候更新过的节点作为出发节点所连接的边进行松弛就够了。如何记录上次松弛的时候更新过的节点呢?用队列来记录。

模拟过程:示例给出的边为例:
5 6 -2
1 2 1
5 3 1
2 5 2
2 4 -3
4 6 4
1 3 5
使用minDist数组来表达起点到各个节点的最短距离。
初始化起点为节点1, 起点到起点的最短距离为0,所以minDist[1] 为 0。 将节点1 加入队列(下次松弛从节点1开始)

从队列里取出节点1,松弛节点1 作为出发点连接的边(节点1 -> 节点2)和边(节点1 -> 节点3)。边:节点1 -> 节点2,权值为1 ,minDist[2] > minDist[1] + 1 ,更新 minDist[2] = minDist[1] + 1 = 0 + 1 = 1。边:节点1 -> 节点3,权值为5 ,minDist[3] > minDist[1] + 5,更新 minDist[3] = minDist[1] + 5 = 0 + 5 = 5。将节点2、节点3 加入队列。

在这里插入图片描述

从队列里取出节点2,松弛节点2 作为出发点连接的边(节点2 -> 节点4)和边(节点2 -> 节点5)。边:节点2 -> 节点4,权值为1 ,minDist[4] > minDist[2] + (-3) ,更新 minDist[4] = minDist[2] + (-3) = 1 + (-3) = -2。边:节点2 -> 节点5,权值为2 ,minDist[5] > minDist[2] + 2,更新 minDist[5] = minDist[2] + 2 = 1 + 2 = 3。将节点4,节点5 加入队列。
在这里插入图片描述

在过程中,用visited数组记录已经在队列里的元素,已经在队列的元素不用重复加入。后面过程同上述。

代码实现:在上面模拟过程中,我们每次都要知道一个节点作为出发点连接了哪些节点。如果想方便知道这些数据,就需要使用邻接表来存储这个图。

拓展:while (!que.empty()) 队里会不会造成死循环?图中有环,这样一直有元素加入到队列里?要看它是正权回路还是负权回路。
正权回路就是有环,但环的总权值为正数。在有环且只有正权回路的情况下,即使元素重复加入队列,最后,也会因为所有边都松弛后,节点数值(minDist数组)不在发生变化了而终止。(而且有重复元素加入队列是正常的,多条路径到达同一个节点,节点必要要选择一个最短的路径,而这个节点就会重复加入队列进行判断,选一个最短的)。所以本题我们使用队列优化,有元素重复加入队列,也会因为最后 minDist数组 不会在发生变化而终止。

import java.util.*;

public class Main{
    static class Edge{
        int from;
        int to;
        int val;
        public Edge(int from,int to,int val){
            this.from=from;
            this.to=to;
            this.val=val;
        }
    }

    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();
        int m=sc.nextInt();
        List<List<Edge>> graph=new ArrayList<>();

        for(int i=0;i<=n;i++){
            graph.add(new ArrayList<>());
        }
        for(int i=0; i<m; i++) {
            int from = sc.nextInt();
            int to = sc.nextInt();
            int val = sc.nextInt();
            graph.get(from).add(new Edge(from, to, val));
        }

        int[] mindist=new int[n+1];
        Arrays.fill(mindist,Integer.MAX_VALUE);
        mindist[1]=0;

        Queue<Integer> queue=new LinkedList<>();
        queue.offer(1);
        boolean[] isinque=new boolean[n+1];
        while(!queue.isEmpty()){
            int curnode=queue.poll();
            isinque[curnode]=false;
            for(Edge edge:graph.get(curnode)){
                if(mindist[edge.to]> mindist[edge.from] + edge.val){
                    mindist[edge.to] = mindist[edge.from] + edge.val;
                    if(!isinque[edge.to]){
                        queue.offer(edge.to);
                        isinque[edge.to]=true;
                    }
                }
            }
        }
        if (mindist[n] == Integer.MAX_VALUE) {
            System.out.println("unconnected");
        } else {
            System.out.println(mindist[n]);
        }
    }
}

Bellman_ford之判断负权回路

题目:某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。然而,在评估从城市 1 到城市 n 的所有可能路径中综合政府补贴后的最低运输成本时,存在一种情况:图中可能出现负权回路。负权回路是指一系列道路的总权值为负,这样的回路使得通过反复经过回路中的道路,理论上可以无限地减少总成本或无限地增加总收益。为了避免货物运输商采用负权回路这种情况无限的赚取政府补贴,算法还需检测这种特殊情况。
请找出从城市 1 到城市 n 的所有可能路径中,综合政府补贴后的最低运输成本。同时能够检测并适当处理负权回路的存在。城市 1 到城市 n 之间可能会出现没有路径的情况。
输入描述:第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v。
输出描述:如果没有发现负权回路,则输出一个整数,表示从城市 1 到城市 n 的最低运输成本(包括政府补贴)。如果该整数是负数,则表示实现了盈利。如果发现了负权回路的存在,则输出 “circle”。如果从城市 1 无法到达城市 n,则输出 “unconnected”。
链接:https://kamacoder.com/problempage.php?pid=1153

bellman_ford 算法的核心:对所有边进行 n-1 次松弛。
松弛n次以上会怎么样?在没有负权回路的图中,松弛 n 次以上,结果不会有变化。但本题有负权回路,如果松弛 n 次,结果就会有变化了,因为有负权回路 就是可以无限最短路径(一直绕圈,就可以一直得到无限小的最短距离)。那么每松弛一次,都会更新最短路径,所以结果会一直有变化。

解决本题的核心思路,就是在94城市间货物运输I 的基础上,再多松弛一次,看minDist数组是否发生变化,如果出现minDist出现变化就判断有负权回路。

import java.util.*;

public class Main {
    // 基于Bellman_ford-SPFA方法
    static class Edge {
        int from;
        int to;
        int val;
        public Edge(int from, int to, int val) {
            this.from = from;
            this.to = to;
            this.val = val;
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        List<List<Edge>> graph = new ArrayList<>();

        for (int i = 0; i <= n; i++) {
            graph.add(new ArrayList<>());
        }

        for (int i = 0; i < m; i++) {
            int from = sc.nextInt();
            int to = sc.nextInt();
            int val = sc.nextInt();
            graph.get(from).add(new Edge(from, to, val));
        }

        int[] minDist = new int[n + 1];
        Arrays.fill(minDist, Integer.MAX_VALUE);
        minDist[1] = 0;
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(1);

        int[] count = new int[n + 1];
        count[1]++;

        boolean[] isInQueue = new boolean[n + 1];
        boolean flag = false;

        while (!queue.isEmpty()) {
            int curNode = queue.poll();
            isInQueue[curNode] = false; 
            for (Edge edge : graph.get(curNode)) {
                if (minDist[edge.to] > minDist[edge.from] + edge.val) { 
                    minDist[edge.to] = minDist[edge.from] + edge.val;
                    if (!isInQueue[edge.to]) {
                        queue.offer(edge.to);
                        count[edge.to]++;
                        isInQueue[edge.to] = true;
                    }

                    if (count[edge.to] == n) { 
                        flag = true;
                        while (!queue.isEmpty()) queue.poll();
                        break;
                    }
                }
            }
        }

        if (flag) {
            System.out.println("circle");
        } else if (minDist[n] == Integer.MAX_VALUE) {
            System.out.println("unconnected");
        } else {
            System.out.println(minDist[n]);
        }
    }
}

Bellman_ford之单源有限最短路

题目:某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。请计算在最多经过 k 个城市的条件下,从城市 src 到城市 dst 的最低运输成本。
输入描述:第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v。最后一行包含三个正整数,src、dst、和 k,src 和 dst 为城市编号,从 src 到 dst 经过的城市数量限制。
输出描述:输出一个整数,表示从城市 src 到城市 dst 的最低运输成本,如果无法在给定经过城市数量限制下找到从 src 到 dst 的路径,则输出 “unreachable”,表示不存在符合条件的运输方案。
链接:https://kamacoder.com/problempage.php?pid=1154

题目中描述是最多经过 k 个城市的条件下,而不是一定经过k个城市,也可以经过的城市数量比k小,但要最短的路径。
对所有边松弛一次,相当于计算起点到达与起点一条边相连的节点的最短距离。节点数量为n,起点到终点,最多是 n-1 条边相连。 那么对所有边松弛 n-1 次 就一定能得到起点到达终点的最短距离。
本题是最多经过 k 个城市,那么是 k + 1条边相连的节点。所以本题就是求起点最多经过k + 1 条边到达终点的最短距离。

此题还有一个要注意的点:
理论上来说,对所有边松弛一次,相当于计算起点到达与起点一条边相连的节点的最短距离。对所有边松弛三次,以此类推。但在对所有边松弛第一次的过程中,会发现不仅仅与起点一条边相连的节点更新了,所有节点都更新了。而且对所有边的后面几次松弛,同样是更新了所有的节点,说明至多经过k 个节点 这个限制根本没有限制住,每个节点的数值都被更新了。
计算minDist数组的时候,基于了本次松弛的 minDist数值,而不是上一次松弛时候minDist的数值。所以在每次计算 minDist 时候,要基于 对所有边上一次松弛的 minDist 数值才行,所以我们要记录上一次松弛的minDist。

和之前两个题的区别在于:
94城市间货物运输I,是没有负权回路的,那么多松弛多少次,对结果都没有影响。求节点1到节点n的最短路径,松弛n-1 次就够了,松弛大于 n-1次,结果也不会变。那么在对所有边进行第一次松弛的时候,如果基于本次计算的 minDist 来计算 minDist (相当于多做松弛了),也是对最终结果没影响。
95城市间货物运输II是判断是否有负权回路,一旦有负权回路, 对所有边松弛 n-1 次以后,在做松弛 minDist 数值一定会变,根据这一点来判断是否有负权回路。所以,95城市间货物运输II 只需要判断minDist数值变化了就行,而 minDist 的数值对不对,并不是我们关心的。
本题为什么计算minDist 一定要基于上次的minDist 数值。其关键在于本题的两个因素:本题可以有负权回路,说明只要多做松弛,结果是会变的。本题要求最多经过k个节点,对松弛次数是有限制的。

import java.util.*;

public class Main {
    // 基于Bellman_for一般解法解决单源最短路径问题
    static class Edge {
        int from;
        int to;
        int val;
        public Edge(int from, int to, int val) {
            this.from = from;
            this.to = to;
            this.val = val;
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();

        List<Edge> graph = new ArrayList<>();

        for (int i = 0; i < m; i++) {
            int from = sc.nextInt();
            int to = sc.nextInt();
            int val = sc.nextInt();
            graph.add(new Edge(from, to, val));
        }

        int src = sc.nextInt();
        int dst = sc.nextInt();
        int k = sc.nextInt();

        int[] minDist = new int[n + 1];
        int[] minDistCopy;

        Arrays.fill(minDist, Integer.MAX_VALUE);
        minDist[src] = 0;

        for (int i = 0; i < k + 1; i++) {
            minDistCopy = Arrays.copyOf(minDist, n + 1);
            for (Edge edge : graph) {
                int from = edge.from;
                int to = edge.to;
                int val = edge.val;
                if (minDistCopy[from] != Integer.MAX_VALUE && minDist[to] > minDistCopy[from] + val) {
                    minDist[to] = minDistCopy[from] + val;
                }
            }
        }
        
        if (minDist[dst] == Integer.MAX_VALUE) {
            System.out.println("unreachable");
        } else {
            System.out.println(minDist[dst]);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值