码随想录算法训练营第61天|卡码网:94. 城市间货物运输 I、95. 城市间货物运输 II、96. 城市间货物运输 III

1.卡码网:94. 城市间货物运输 I

题目链接:https://kamacoder.com/problempage.php?pid=1152
文章链接:https://www.programmercarl.com/kamacoder/0094.城市间货物运输I-SPFA.html

在这里插入图片描述

思路:
只对 上一次松弛的时候更新过的节点作为出发节点所连接的边 进行松弛就够了,不需要对所有边进行松弛。
基于以上思路,如何记录 上次松弛的时候更新过的节点呢?
用队列来记录。(其实用栈也行,对元素顺序没有要求)
针对:图中边的权值可以有负数,且不存在任何负权回路的情况。(可以为正权回路)

import java.util.*;

public class Main {

    // Define an inner class Edge
    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) {
        // Input processing
        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));
        }

        // Declare the minDist array to record the minimum distance form current node to the original node
        int[] minDist = new int[n + 1];
        Arrays.fill(minDist, Integer.MAX_VALUE);
        minDist[1] = 0;

        // Declare a queue to store the updated nodes instead of traversing all nodes each loop for more efficiency
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(1);

        // Declare a boolean array to record if the current node is in the queue to optimise the processing
        boolean[] isInQueue = new boolean[n + 1];

        while (!queue.isEmpty()) {
            int curNode = queue.poll();
            isInQueue[curNode] = false; // Represents the current node is not in the queue after being polled
            for (Edge edge : graph.get(curNode)) {
                if (minDist[edge.to] > minDist[edge.from] + edge.val) { // Start relaxing the edge
                    minDist[edge.to] = minDist[edge.from] + edge.val;
                    if (!isInQueue[edge.to]) { // Don't add the node if it's already in the queue
                        queue.offer(edge.to);
                        isInQueue[edge.to] = true;
                    }
                }
            }
        }
        
        // Outcome printing
        if (minDist[n] == Integer.MAX_VALUE) {
            System.out.println("unconnected");
        } else {
            System.out.println(minDist[n]);
        }
    }
}

2.卡码网:95. 城市间货物运输 II

题目链接:https://kamacoder.com/problempage.php?pid=1153
文章链接:https://www.programmercarl.com/kamacoder/0095.城市间货物运输II.html

在这里插入图片描述

思路:

  1. 使用 Bellman_ford 算法判断负权回路
    在 bellman_ford 算法中,松弛 n-1 次所有的边 就可以求得 起点到任何节点的最短路径,松弛 n 次以上,minDist数组(记录起到到其他节点的最短距离)中的结果也不会有改变 。
    而本题有负权回路的情况下,一直都会有更短的最短路,所以 松弛 第n次,minDist数组 也会发生改变。
    那么解决本题的 核心思路,就是在 kama94.城市间货物运输I 的基础上,再多松弛一次,看minDist数组 是否发生变化。若变化,则表示出现负权回路;否则,没有。
    注意:若题目中没有提前声明图中是否没有负权回路,则对于 在有负权值的图中求最短路,都需要先看看这个图里有没有负权回路。若有,在这样的图中求最短路的话, 就会在这个环里无限循环 (也是负数+负数 只会越来越小),无法求出最短路径。
import java.util.*;

public class Main {
    public static void main (String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt(); // 城市数量
        int m = sc.nextInt(); // 道路数量
        
        List<List<Integer>> roadList = new ArrayList<>();
        for (int i=0;i<m;i++) {
            int s = sc.nextInt();
            int t = sc.nextInt();
            int v = sc.nextInt();
            List<Integer> list = new ArrayList<>();
            list.add(s);
            list.add(t);
            list.add(v);
            roadList.add(list);
        }
        
        int[] minDist = new int[n+1]; // 记录起点到各个节点的最短距离
        Arrays.fill(minDist,Integer.MAX_VALUE);
        minDist[1]=0;
        boolean flag = false; // 判断是否出现负权回路
        
        // 松弛n-1次
        for(int j=1;j<=n;j++) { // 注意这里松弛了n下
            // 对所有道路进行分析
            for(int i=0;i<m;i++) {
                int from = roadList.get(i).get(0);
                int to = roadList.get(i).get(1);
                int value = roadList.get(i).get(2);
                if (j < n) { 
                    if (minDist[from] != Integer.MAX_VALUE && minDist[from] + value < minDist[to]) {
                        minDist[to] = minDist[from] + value;
                        
                    }
                } else { // 多松弛一下,检查是否数组有变化
                    if (minDist[from] != Integer.MAX_VALUE && minDist[from] + value < minDist[to]) {
                       flag = true; // 出现负权回路
                    }
                }
                
            }
        }
        if (flag) {
            System.out.println("circle");
            return;
        }
        
        if (minDist[n] == Integer.MAX_VALUE) {
            System.out.println("unconnected");
        } else {
            System.out.println(minDist[n]);
        }
        
    }
}
  1. 使用 队列优化版的bellman_ford(SPFA)判断是否有负权回路。
    在 0094.城市间货物运输I-SPFA 中,在极端情况下,即:所有节点都与其他节点相连,每个节点的入度为 n-1 (n为节点数量),所以每个节点最多加入 n-1 次队列。
    那么如果节点加入队列的次数 超过了 n-1次 ,那么该图就一定有负权回路。
import java.util.*;

public class Main {
    // 基于Bellman_ford-SPFA方法
    // Define an inner class Edge
    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) {
        // Input processing
        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));
        }

        // Declare the minDist array to record the minimum distance form current node to the original node
        int[] minDist = new int[n + 1];
        Arrays.fill(minDist, Integer.MAX_VALUE);
        minDist[1] = 0;

        // Declare a queue to store the updated nodes instead of traversing all nodes each loop for more efficiency
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(1);

        // Declare an array to record the times each node has been offered in the queue
        int[] count = new int[n + 1];// 记录节点加入队列几次
        count[1]++;

        // Declare a boolean array to record if the current node is in the queue to optimise the processing
        boolean[] isInQueue = new boolean[n + 1];

        // Declare a boolean value to check if there is a negative weight loop inside the graph
        boolean flag = false;

        while (!queue.isEmpty()) {
            int curNode = queue.poll();
            isInQueue[curNode] = false; // Represents the current node is not in the queue after being polled
            for (Edge edge : graph.get(curNode)) {
                if (minDist[edge.to] > minDist[edge.from] + edge.val) { // Start relaxing the edge
                    minDist[edge.to] = minDist[edge.from] + edge.val;
                    if (!isInQueue[edge.to]) { // Don't add the node if it's already in the queue
                        queue.offer(edge.to);
                        count[edge.to]++;
                        isInQueue[edge.to] = true;
                    }

                    if (count[edge.to] == n) { // 如果加入队列次数超过 n-1次 就说明该图与负权回路
                        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]);
        }
    }
}

3.卡码网:96. 城市间货物运输 III

题目链接:https://kamacoder.com/problempage.php?pid=1154
文章链接:https://www.programmercarl.com/kamacoder/0096.城市间货物运输III.html

在这里插入图片描述

思路:

  1. 使用 Bellman_ford 算法
    题目中描述是 最多经过 k 个城市的条件下,而不是一定经过k个城市,也可以经过的城市数量比k小,但要最短的路径。
    最多经过k个城市等价于最多经过k + 1 条边。
    由于对所有边松弛一次,相当于计算 起点到达 与起点一条边相连的节点 的最短距离,那么对所有边松弛 k + 1次,就是求 起点到达 与起点k + 1条边相连的节点的 最短距离。若松弛k+1次后,终点城市权值有变化,则表示最多经过k+1条边能够到达终点城市,此时权值就是从起点到终点的最低运输成本;若终点城市权值仍然是初始值,则表示k+1个边无法到达。
    注意:
    第n次松弛必须保证只会更新与起点相连n条边的节点的最短距离,不会更新其他情况的节点,且与起点相连n条边的节点的最短距离是由上一次松弛的结果获得的,而不是当前松弛的结果获得。当前松弛结果只会影响下一次松弛结果。
    所以在每次计算 minDist 时候,要基于 对所有边上一次松弛的 minDist 数值才行,因此我们要记录上一次松弛的minDist。只有这样前面节点松弛的结果才不会影响剩余节点的权值。
import java.util.*;

public class Main {
    public static void main (String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt(); // 城市数量
        int m = sc.nextInt(); // 道路数量
        
        List<List<Integer>> roadList = new ArrayList<>();
        for (int i=0;i<m;i++) {
            int s = sc.nextInt();
            int t = sc.nextInt();
            int v = sc.nextInt();
            List<Integer> list = new ArrayList<>();
            list.add(s);
            list.add(t);
            list.add(v);
            roadList.add(list);
        }
        int src = sc.nextInt();
        int dst = sc.nextInt();
        int k = sc.nextInt();
        
        int[] minDist = new int[n+1]; // 记录起点到各个节点的最短距离
        Arrays.fill(minDist,Integer.MAX_VALUE);
        minDist[src]=0;
        
        // 松弛k+1次
        for(int j=1;j<=k+1;j++) {
            int[] minDistCopy = Arrays.copyOf(minDist,minDist.length); // 该数组保证每次松弛节点权值的更新只受上一次松弛结果的影响
            // 对所有道路进行分析
            for(int i=0;i<m;i++) {
                int from = roadList.get(i).get(0);
                int to = roadList.get(i).get(1);
                int value = roadList.get(i).get(2);
                if (minDistCopy[from] != Integer.MAX_VALUE && minDistCopy[from] + value < minDist[to]) {
                    minDist[to] = minDistCopy[from] + value;
                } // 使用上一次松弛的结果使得这次松弛只会更新以上一次松弛更新节点为起点的边的终点节点的权值。
            }
        }
        
        if (minDist[dst] == Integer.MAX_VALUE) {
            System.out.println("unreachable");
        } else {
            System.out.println(minDist[dst]);
        }
        
    }
}
  1. 使用 队列优化版的bellman_ford(SPFA)
    使用SPFA算法解决本题的时候,关键在于 如何控制松弛k次。可以用一个变量 que_size 记录每一轮松弛入队列的所有节点数量。下一轮松弛的时候,就把队列里 que_size 个节点都弹出来,就是上一轮松弛入队列的节点。
import java.util.*;

public class Main {
    static class Edge { // 邻接表中的边
        int to;  // 连接的节点
        int val; // 边的权重

        Edge(int t, int w) {
            to = t;
            val = w;
        }
    }

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

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

        // 将所有边保存到邻接表中
        for (int i = 0; i < m; i++) {
            int p1 = scanner.nextInt();
            int p2 = scanner.nextInt();
            int val = scanner.nextInt();
            grid.get(p1).add(new Edge(p2, val));
        }

        int start = scanner.nextInt();
        int end = scanner.nextInt();
        int k = scanner.nextInt();

        k++;

        int[] minDist = new int[n + 1];
        Arrays.fill(minDist, Integer.MAX_VALUE);
        int[] minDistCopy = new int[n + 1]; // 用来记录每一次遍历的结果

        minDist[start] = 0;

        Queue<Integer> queue = new LinkedList<>();
        queue.add(start); // 队列里放入起点

        int queueSize;
        while (k-- > 0 && !queue.isEmpty()) {
            boolean[] visited = new boolean[n + 1]; // 每一轮松弛中,控制节点不用重复入队列
            minDistCopy = minDist.clone(); // 获取上一次计算的结果
            queueSize = queue.size(); // 记录上次入队列的节点个数
            while (queueSize-- > 0) { // 上一轮松弛入队列的节点,这次对应的边都要做松弛
                int node = queue.poll();
                for (Edge edge : grid.get(node)) {
                    int from = node;
                    int to = edge.to;
                    int price = edge.val;
                    if (minDist[to] > minDistCopy[from] + price) {
                        minDist[to] = minDistCopy[from] + price;
                        if (visited[to]) continue; // 不用重复放入队列,但需要重复松弛,所以放在这里位置
                        visited[to] = true;
                        queue.add(to);
                    }
                }
            }
        }

        if (minDist[end] == Integer.MAX_VALUE) {
            System.out.println("unreachable");
        } else {
            System.out.println(minDist[end]);
        }
    }
}
随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法。 总之,代随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值