代码随想录算法训练营第六十六天 | Bellman_ford 队列优化算法(SPFA)、Bellman_ford之判断负权回路、Bellman_ford之单源有限最短路、复习

Bellman_ford 队列优化算法(SPFA)

题目链接:https://kamacoder.com/problempage.php?pid=1152
文档讲解:https://programmercarl.com/kamacoder/0094.%E5%9F%8E%E5%B8%82%E9%97%B4%E8%B4%A7%E7%89%A9%E8%BF%90%E8%BE%93I-SPFA.html

思路

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

代码

import java.util.*;
class Edge {
    int to;
    int val;
    Edge(int to, int val) {
        this.to = to;
        this.val = val;
    }
}
class Main{
    public static void main (String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt(), m = in.nextInt();
        int[] minDist = new int[n + 1];
        Arrays.fill(minDist, Integer.MAX_VALUE);
        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 s = in.nextInt(), t = in.nextInt(), val = in.nextInt();
            grid.get(s).add(new Edge(t, val));
        }
        int start = 1, end = n;
        minDist[start] = 0;
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(start);
        while (!queue.isEmpty()) {
            int node = queue.poll();
            for (Edge edge : grid.get(node)) {
                int to = edge.to, price = edge.val;
                if (minDist[to] > minDist[node] + price) {
                    minDist[to] = minDist[node] + price;
                    queue.offer(to);
                }
            }
        }
        System.out.println(minDist[end] == Integer.MAX_VALUE ? "unconnected" : minDist[end]);
    }
}

Bellman_ford之判断负权回路

题目链接:https://kamacoder.com/problempage.php?pid=1153
文档讲解:https://programmercarl.com/kamacoder/0095.%E5%9F%8E%E5%B8%82%E9%97%B4%E8…

思路

在 Bellman_ford 算法中,松弛 n-1 次所有的边 就可以求得 起点到任何节点的最短路径,松弛 n 次以上,minDist数组(记录起到到其他节点的最短距离)中的结果也不会有改变。而本题有负权回路的情况下,一直都会有更短的最短路,所以松弛第n次,minDist数组也会发生改变。那么解决本题的 核心思路,就是在 kama94.城市间货物运输I 的基础上,再多松弛一次,看minDist数组是否发生变化。

代码

基于标准Bellman_ford

import java.util.*;
class Main{
    public static void main (String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt(), m = in.nextInt();
        int[][] grid = new int[m][3];
        for (int i = 0; i < m; i++) {
            grid[i][0] = in.nextInt();
            grid[i][1] = in.nextInt();
            grid[i][2] = in.nextInt();
        }
        int start = 1, end = n;
        int[] minDist = new int[n + 1];
        Arrays.fill(minDist, Integer.MAX_VALUE);
        minDist[start] = 0;
        boolean flag = false; // 默认没有负权回路
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < m; j++) {
                int from = grid[j][0], to = grid[j][1], price = grid[j][2];
                if (i < n) { // 前n - 1次松弛
                    if (minDist[from] != Integer.MAX_VALUE && minDist[from] + price < minDist[to]) {
                        minDist[to] = minDist[from] + price;
                    }
                } else { // 判断第n次松弛后minDist是否发生变化
                    if (minDist[from] != Integer.MAX_VALUE && minDist[from] + price < minDist[to]) {
                        flag = true;
                        break;
                    }
                }
            }
        }
        if (flag) System.out.println("circle");
        else if (minDist[end] == Integer.MAX_VALUE) System.out.println("unconnected");
        else System.out.println(minDist[end]);
    }
}

基于SPFA

import java.util.*;
class Edge {
    int to;
    int val;
    Edge(int to, int val) {
        this.to = to;
        this.val = val;
    }
}
class Main{
    public static void main (String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt(), m = in.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 from = in.nextInt(), to = in.nextInt(), val = in.nextInt();
            grid.get(from).add(new Edge(to, val));
        }
        int[] minDist = new int[n + 1];
        int[] count = new int[n + 1]; // 记录节点被加入队列的次数
        Arrays.fill(minDist, Integer.MAX_VALUE);
        minDist[1] = 0;
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(1);
        count[1]++;
        while (!queue.isEmpty()) {
            int cur = queue.poll();
            for (Edge edge : grid.get(cur)) {
                int from = cur, to = edge.to, price = edge.val;
                if (minDist[to] > minDist[from] + price) {
                    minDist[to] = minDist[from] + price;
                    queue.offer(to);
                    count[to]++;
                    if (count[to] == n) { // 节点入队次数大于n - 1,说明有负权回路
                        System.out.println("circle");
                        return;
                    }
                }
            }
        }
        if (minDist[n] == Integer.MAX_VALUE) System.out.println("unconnected");
        else System.out.println(minDist[n]);
    }
}

Bellman_ford之单源有限最短路

题目链接:https://kamacoder.com/problempage.php?pid=1154
文档讲解:https://programmercarl.com/kamacoder/0096.%E5%9F%8E%E5%B8%82%E9%97%B4%E8%B4%A7%E7%89%A9%E8…

思路

本题是求:起点最多经过k + 1 条边到达终点的最短距离。对所有边松弛一次,相当于计算起点到达与起点一条边相连的节点的最短距离,那么对所有边松弛 k + 1次,就是求起点到达与起点k + 1条边相连的节点的最短距离。Bellman_ford 标准写法是松弛 n-1 次,本题就松弛 k + 1次就好。
但是如果根据标准的Bellman_ford来解决,得到的结果是不对的。理论上来说,对所有边松弛一次,相当于计算 起点到达与起点一条边相连的节点的最短距离。但在对所有边松弛第一次的过程中,通过打印minDist数组可以发现,不仅仅与起点一条边相连的节点更新了,所有节点都更新了。而且对所有边的后面几次松弛,同样是更新了所有的节点,说明至多经过k个节点这个限制根本没有限制住,每个节点的数值都被更新了。
这是因为在代码中,计算minDist数组的时候,基于了本次松弛的minDist数值,而不是上一次 松弛时候minDist的数值。所以在每次计算minDist时候,要基于对所有边上一次松弛的minDist数值才行,所以我们要记录上一次松弛的minDist

代码

// 标准Bellman_ford
import java.util.*;
class Main{
    public static void main (String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt(), m = in.nextInt();
        int[][] grid = new int[m][3];
        for (int i = 0; i < m; i++) {
            grid[i][0] = in.nextInt();
            grid[i][1] = in.nextInt();
            grid[i][2] = in.nextInt();
        }
        int src = in.nextInt(), dst = in.nextInt(), k = in.nextInt();
        int[] minDist = new int[n + 1];
        Arrays.fill(minDist, Integer.MAX_VALUE);
        minDist[src] = 0;
        int[] minDist_copy = new int[n + 1];
        for (int i = 1; i <= k + 1; i++) {
            minDist_copy = Arrays.copyOf(minDist, minDist.length);
            for (int[] edge : grid) {
                int from = edge[0], to = edge[1], val = edge[2];
                // 用minDist_copy来计算minDist[to]
                if (minDist_copy[from] != Integer.MAX_VALUE && minDist[to] > minDist_copy[from] + val) {
                    minDist[to] = minDist_copy[from] + val;
                }
            }
        }
        System.out.println(minDist[dst] == Integer.MAX_VALUE ? "unreachable" : minDist[dst]);
    }
}

复习二叉树部分

530.二叉搜索树的最小绝对差
501.二叉搜索树中的众数
236. 二叉树的最近公共祖先

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值