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

SPFA算法

Bellman_ford 队列优化算法 ,也叫SPFA算法(Shortest Path Faster Algorithm)。

传统方法:Bellman-Ford算法需要对每一条边在每次迭代中进行检查,这可能导致对所有顶点反复执行不必要的松弛操作,尤其是在图较大而边数较多的情况下。

使用队列:只对那些其最短路径估计值发生变化的节点进行松弛操作,因为只有这些节点可能影响其邻接节点的路径估计值。

代码:

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

        // 读取顶点数量n和边数量m
        int n = scanner.nextInt(); // 顶点数量
        int m = scanner.nextInt(); // 边数量

        // 使用HashMap存储邻接表,每个顶点映射到一个边列表
        HashMap<Integer, List<int[]>> map = new HashMap<>(n + 1);

        // 读取边的信息并构建邻接表
        for (int i = 0; i < m; i++) {
            int s = scanner.nextInt(); // 边的起始顶点
            int t = scanner.nextInt(); // 边的终止顶点
            int val = scanner.nextInt(); // 边的权重

            // 如果起始顶点s不在map中,初始化一个新的边列表
            if (!map.containsKey(s)) {
                map.put(s, new ArrayList<>()); // 初始化新的边列表
            }

            // 将边的信息(s, t, val)加入到起始顶点s的边列表中
            map.get(s).add(new int[]{s, t, val});
        }

        // 初始化数组,用于存储从起点到每个顶点的最短路径估计值
        int[] minDist = new int[n + 1];
        Arrays.fill(minDist, Integer.MAX_VALUE); // 初始设为无穷大
        int start = 1; // 起始节点,假设从节点1开始
        int end = n; // 终点,假设目标是到达节点n
        minDist[start] = 0; // 起始节点到自身的距离为0

        // 使用双端队列(Deque)实现队列,用于维护待处理的顶点
        Deque<Integer> deque = new LinkedList<>();
        deque.addLast(start); // 将起始节点加入队列

        // 当队列不为空时,进行松弛操作
        while (!deque.isEmpty()) {
            Integer node = deque.pollFirst(); // 取出队首节点
            List<int[]> list = map.get(node); // 获取当前节点的边列表

            // 如果当前节点有边
            if (list != null) {
                // 遍历当前节点的每条边
                for (int[] ints : list) {
                    int s = ints[0]; // 起始顶点
                    int t = ints[1]; // 终止顶点
                    int val = ints[2]; // 边的权重

                    // 如果从起点到s的距离不为无穷大,并且可以通过s到t找到更短路径
                    if (minDist[s] != Integer.MAX_VALUE && minDist[s] + val < minDist[t]) {
                        minDist[t] = minDist[s] + val; // 更新最短路径估计值
                        deque.addLast(t); // 将终止顶点t加入队列
                    }
                }
            }
        }

        // 输出结果,如果终点end的最短路径仍为无穷大,表示不可达,否则输出最短路径值
        System.out.println(minDist[end] == Integer.MAX_VALUE ? "unconnected" : minDist[end]);

        scanner.close(); // 关闭扫描器
    }

95. 城市间货物运输 II

在这里插入图片描述

bellman_ford之判断负权回路

有负权回路时,如果松弛 n 次,结果就会有变化了,因为 有负权回路 就是可以无限最短路径(一直绕圈,就可以一直得到无限小的最短距离)。

传统Bellman_ford我们对所有边松弛了n-1次后,在松弛一次,如果出现minDist出现变化就判断有负权回路。

代码实现:

    //邻接表法+负权回路检测
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 读取顶点数量和边数量
        int n = scanner.nextInt(); // 顶点数量(城市数量)
        int m = scanner.nextInt(); // 边数量(运输路线数量)

        // 用于存储图的邻接表,key为顶点,value为该顶点的边的列表
        HashMap<Integer, List<int[]>> map = new HashMap<>();

        // 读取每条边的信息,并填充到邻接表中
        for (int i = 0; i < m; i++) {
            int s = scanner.nextInt(); // 边的起点
            int t = scanner.nextInt(); // 边的终点
            int val = scanner.nextInt(); // 边的权重(运输成本)

            // 如果起点顶点还没有在图中,初始化其邻接表
            if (!map.containsKey(s)) {
                map.put(s, new ArrayList<>());
            }

            // 将边信息添加到起点顶点的邻接表中
            List<int[]> list = map.get(s);
            list.add(new int[]{s, t, val});
        }

        // 初始化最短路径距离数组
        int[] minDist = new int[n + 1];
        Arrays.fill(minDist, Integer.MAX_VALUE); // 将所有顶点的距离初始化为无穷大

        int start = 1, end = n; // 起点为1,终点为n
        minDist[start] = 0; // 起点到自身的距离为0
        boolean flag = false;
        // 执行Bellman-Ford算法,进行n-1次松弛操作
        for (int i = 1; i <= n; i++) {
            // 遍历每个顶点
            for (int j = 1; j <= n; j++) {
                List<int[]> list = map.get(j);
                // 如果顶点j有邻接边
                if (list != null) {
                    // 遍历顶点j的所有边
                    for (int[] ints : list) {
                        int s = ints[0]; // 边的起点
                        int t = ints[1]; // 边的终点
                        int val = ints[2]; // 边的权重
                        // 执行松弛操作:如果从s到t的路径通过s的距离更短,则更新t的距离
                        if (minDist[s] != Integer.MAX_VALUE && minDist[t] > minDist[s] + val) {
                            // 如果在第n次迭代中仍然可以更新某个节点的最短距离,说明图中存在负权回路
                            if (i == n) {
                                flag = true;
                            }
                            minDist[t] = minDist[s] + val;
                        }
                    }
                }
            }
        }

        // 输出最终结果
        if (minDist[end] == Integer.MAX_VALUE) {
            // 如果终点不可达,则输出"unconnected"
            System.out.println("unconnected");
        } else if (flag) {
            System.out.println("circle");
        } else {
            // 否则输出从起点到终点的最短路径长度
            System.out.println(minDist[end]);
        }
        scanner.close();
    }

SPFA算法我们可以使用一个数组count来记录每个节点的入队次数,当某个节点的入队次数超过n,可以立即判断存在负权回路。

代码实现:

    // 使用队列优化Bellman-Ford算法 + 负权回路检测
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 读取顶点数量n和边数量m
        int n = scanner.nextInt(); // 顶点数量
        int m = scanner.nextInt(); // 边数量

        // 使用HashMap存储邻接表,每个顶点映射到一个边列表
        HashMap<Integer, List<int[]>> map = new HashMap<>(n + 1);

        // 读取边的信息并构建邻接表
        for (int i = 0; i < m; i++) {
            int s = scanner.nextInt(); // 边的起始顶点
            int t = scanner.nextInt(); // 边的终止顶点
            int val = scanner.nextInt(); // 边的权重

            // 如果起始顶点s不在map中,初始化一个新的边列表
            if (!map.containsKey(s)) {
                map.put(s, new ArrayList<>()); // 初始化新的边列表
            }

            // 将边的信息(s, t, val)加入到起始顶点s的边列表中
            map.get(s).add(new int[]{s, t, val});
        }

        // 初始化数组,用于存储从起点到每个顶点的最短路径估计值
        int[] minDist = new int[n + 1];
        Arrays.fill(minDist, Integer.MAX_VALUE); // 初始设为无穷大
        int start = 1; // 起始节点,假设从节点1开始
        int end = n; // 终点,假设目标是到达节点n
        minDist[start] = 0; // 起始节点到自身的距离为0

        // 使用数组记录每个节点的入队次数
        int[] count = new int[n + 1]; // 入队次数计数器

        // 使用双端队列(Deque)实现队列,用于维护待处理的顶点
        Deque<Integer> deque = new LinkedList<>();
        deque.addLast(start); // 将起始节点加入队列
        count[start] = 1; // 起始节点入队一次

        boolean hasNegativeCycle = false; // 标记负权回路

        // 当队列不为空时,进行松弛操作
        while (!deque.isEmpty()) {
            Integer node = deque.pollFirst(); // 取出队首节点
            List<int[]> list = map.get(node); // 获取当前节点的边列表

            // 如果当前节点有边
            if (list != null) {
                // 遍历当前节点的每条边
                for (int[] ints : list) {
                    int s = ints[0]; // 起始顶点
                    int t = ints[1]; // 终止顶点
                    int val = ints[2]; // 边的权重

                    // 如果从起点到s的距离不为无穷大,并且可以通过s到t找到更短路径
                    if (minDist[s] != Integer.MAX_VALUE && minDist[s] + val < minDist[t]) {
                        minDist[t] = minDist[s] + val; // 更新最短路径估计值
                        deque.addLast(t); // 将终止顶点t加入队列
                        count[t]++; // 增加终止顶点的入队次数

                        // 检测负权回路
                        if (count[t] > n) {
                            hasNegativeCycle = true;
                            break; // 如果检测到负权回路,直接退出循环
                        }
                    }
                }
            }
            if (hasNegativeCycle) {
                break; // 检测到负权回路后退出外循环
            }
        }

        // 输出结果,如果检测到负权回路,输出"circle"
        if (hasNegativeCycle) {
            System.out.println("circle");
        } else {
            // 否则输出从起点到终点的最短路径,如果不可达,输出"unconnected"
            System.out.println(minDist[end] == Integer.MAX_VALUE ? "unconnected" : minDist[end]);
        }

        scanner.close(); // 关闭扫描器
    }

96. 城市间货物运输 III

在这里插入图片描述

bellman_ford之单源有限最短路

本题是最多经过 k 个城市(不包括起点和终点), 那么是 k + 1条边相连的节点。

bellman_ford 标准写法是松弛 n-1 次,本题就松弛 k + 1次就好。

注意:如果直接在 minDist 上进行更新,可能会干扰到当前轮次的计算结果。所以需要拷贝minDist

代码:

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

        // 读取节点数量和边的数量
        int n = scanner.nextInt(); // 节点数量
        int m = scanner.nextInt(); // 边的数量

        // 使用 HashMap 作为邻接表来表示图
        HashMap<Integer, List<int[]>> map = new HashMap<>(n + 1);
        for (int i = 0; i < m; i++) {
            // 读取每一条边的起点、终点和边的权重
            int s = scanner.nextInt();
            int t = scanner.nextInt();
            int val = scanner.nextInt();
            // 如果起点 s 不在邻接表中,则添加新的条目
            if (!map.containsKey(s)) {
                map.put(s, new ArrayList<>());
            }
            // 将边 (s, t) 和其权重 val 添加到邻接表中
            map.get(s).add(new int[]{s, t, val});
        }

        // 读取起点、终点和最大边数
        int src = scanner.nextInt(); // 起点
        int dst = scanner.nextInt(); // 终点
        int k = scanner.nextInt();  // 最大边数

        // 初始化最小距离数组
        int[] minDist = new int[n + 1];
        Arrays.fill(minDist, Integer.MAX_VALUE); // 默认距离为无穷大
        minDist[src] = 0; // 起点到自身的距离为 0

        // 使用 Bellman-Ford 算法进行 K 次松弛操作
        for (int i = 0; i <= k; i++) {
            // 克隆当前最小距离数组以进行本轮操作
            int[] tempDist = minDist.clone();

            // 遍历每一个节点
            for (int j = 1; j <= n; j++) {
                // 获取节点 j 的所有边
                List<int[]> edges = map.get(j);
                if (edges != null) {
                    // 遍历节点 j 的所有边
                    for (int[] edge : edges) {
                        int s = edge[0]; // 起点
                        int t = edge[1]; // 终点
                        int val = edge[2]; // 边的权重

                        // 如果当前起点的距离不是无穷大,并且可以通过此边缩短终点的距离
                        if (minDist[s] != Integer.MAX_VALUE && tempDist[t] > minDist[s] + val) {
                            // 更新终点的最小距离
                            tempDist[t] = minDist[s] + val;
                        }
                    }
                }
            }

            // 更新最小距离数组
            minDist = tempDist;
        }

        // 输出从起点到终点的最小距离
        if (minDist[dst] == Integer.MAX_VALUE) {
            System.out.println("unreachable"); // 如果终点不可达
        } else {
            System.out.println(minDist[dst]); // 输出最小距离
        }
    }
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值