day57 代码随想录算法训练营 图论专题10

1 今日打卡

Bellman_ford 队列优化算法 94. 城市间货物运输 I

bellman_ford之判断负权回路 95. 城市间货物运输 II

bellman_ford之单源有限最短路 96. 城市间货物运输 III

2 Bellman_ford 队列优化算法

2.1 思路

只松弛 "距离被更新" 的节点:只有节点的最短距离被更新时,其邻接节点的距离才可能被优化,因此只需将这些节点加入队列,避免无意义的遍历;

避免重复入队:用 isInQueue 数组标记节点是否已在队列中,防止同一节点多次入队,减少无效计算;

适用场景:可处理含负权边的图(无负权环),若图中全是正权边,Dijkstra 效率更高。

整体流程:

初始化:起点距离设为 0,其余为无穷大,起点入队并标记;

队列循环:取出队首节点,遍历其所有出边,松弛邻接节点;若邻接节点距离被更新且不在队列中,则入队;

结果判断:终点距离仍为无穷大则不可达,否则输出最短距离。

2.2 实现代码

java 复制代码
import java.util.*;

public class Main {
    // 定义边的内部类:存储边的终点(to)和权重(val)
    static class Link {
        int to;  // 边指向的目标节点编号
        int val; // 这条边的权重(长度)

        // 构造方法:初始化边的终点和权重
        public Link(int to, int val) {
            this.to = to;
            this.val = val;
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // 1. 输入处理:读取节点数n、边数m(节点编号从1到n)
        int n = sc.nextInt(), m = sc.nextInt();

        // 2. 构建邻接表:存储图的边信息(map[i] 存储节点i的所有出边)
        List<List<Link>> map = new ArrayList<>();
        // 初始化邻接表:节点编号从1开始,需初始化n+1个空列表
        for (int i = 0; i <= n; i++) {
            map.add(new ArrayList<>());
        }

        // 3. 读取每条边的信息,填充邻接表
        for (int i = 0; i < m; i++) {
            int from = sc.nextInt(); // 边的起点
            int to = sc.nextInt();   // 边的终点
            int val = sc.nextInt();  // 边的权重
            // 向起点from的出边列表中添加一条指向to、权重为val的边
            map.get(from).add(new Link(to, val));
        }

        // 4. 初始化最短距离数组:minDist[i] 表示起点(1)到节点i的最短距离
        int[] minDist = new int[n + 1];
        // 初始化为无穷大(Integer.MAX_VALUE),表示初始时无法到达
        Arrays.fill(minDist, Integer.MAX_VALUE);
        minDist[1] = 0; // 起点(1)到自身的距离为0

        // 5. 初始化队列标记数组:isInQueue[i] 为true表示节点i已在队列中(避免重复入队)
        boolean[] isInQueue = new boolean[n + 1];
        Queue<Integer> que = new LinkedList<>(); // 存储待松弛的节点
        que.add(1);          // 起点加入队列
        isInQueue[1] = true; // 标记起点在队列中

        // 6. SPFA核心循环:松弛所有可优化的边
        while (!que.isEmpty()) {
            int cur = que.poll(); // 取出队首节点(当前待处理节点)
            isInQueue[cur] = false; // 取消队列标记(允许后续重新入队)

            // 遍历当前节点的所有出边,执行松弛操作
            for (Link link : map.get(cur)) {
                int to = link.to;   // 邻接节点编号
                int val = link.val; // 当前边的权重

                // 松弛条件:
                // 1. 起点到cur的距离不是无穷大(避免Integer.MAX_VALUE + val溢出)
                // 2. 起点到cur的距离 + 边权重 < 起点到to的当前最短距离
                if (minDist[cur] != Integer.MAX_VALUE && minDist[cur] + val < minDist[to]) {
                    minDist[to] = minDist[cur] + val; // 更新最短距离

                    // 若邻接节点不在队列中,加入队列并标记
                    if (!isInQueue[to]) {
                        que.add(to);
                        isInQueue[to] = true;
                    }
                }
            }
        }

        // 7. 输出结果
        if (minDist[n] == Integer.MAX_VALUE) {
            // 终点n的距离仍为无穷大,说明无法到达
            System.out.println("unconnected");
        } else {
            // 输出起点1到终点n的最短距离
            System.out.println(minDist[n]);
        }

        sc.close(); // 关闭扫描器(规范写法)
    }
}

3 bellman_ford之判断负权回路

3.1 思路

Bellman-Ford 基础逻辑:

对图中所有边进行 n-1 轮松弛(n 为节点数),因为任意两点的最短路径最多包含 n-1 条边(无环),n-1 轮松弛后所有可达节点的最短距离已确定。

负权环检测逻辑:
完成 n-1 轮松弛后,再进行第 n 轮松弛。若仍能更新某个节点的最短距离,说明图中存在负权环(因为正常情况下 n-1 轮已能找到最短路径,第 n 轮还能更新,说明路径中存在可无限松弛的负权环)。

3.2 实现代码

java 复制代码
import java.util.*;

public class Main {
    // 定义边的内部类:存储边的起点(l)、终点(r)、权重(val)
    static class edge {
        int l;    // 边的起点
        int r;    // 边的终点
        int val;  // 边的权重(可正可负)

        // 构造方法:初始化边的起点、终点、权重
        public edge(int l, int r, int val) {
            this.l = l;
            this.r = r;
            this.val = val;
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // 1. 输入处理:读取节点数n、边数m(节点编号从1到n)
        int n = sc.nextInt(), m = sc.nextInt();

        // 2. 存储所有边:Bellman-Ford算法直接遍历所有边,无需邻接表
        List<edge> edges = new ArrayList<>();
        for (int i = 0; i < m; i++) {
            int l = sc.nextInt();  // 边的起点
            int r = sc.nextInt();  // 边的终点
            int val = sc.nextInt();// 边的权重
            edges.add(new edge(l, r, val)); // 将边加入列表
        }

        // 3. 初始化最短距离数组:minDist[i] 表示起点(1)到节点i的最短距离
        int[] minDist = new int[n + 1];
        // 初始化为无穷大(Integer.MAX_VALUE),表示初始时无法到达
        Arrays.fill(minDist, Integer.MAX_VALUE);
        minDist[1] = 0; // 起点(1)到自身的距离为0

        // 标记是否存在负权环:true=存在,false=不存在
        boolean flag = false;

        // 4. Bellman-Ford核心循环:共n轮(前n-1轮松弛,第n轮检测负权环)
        for (int i = 1; i <= n; i++) {
            // 遍历所有边,执行松弛操作
            for (edge cur : edges) {
                int from = cur.l;  // 当前边的起点
                int to = cur.r;    // 当前边的终点
                int val = cur.val; // 当前边的权重

                // 跳过起点不可达的情况(避免Integer.MAX_VALUE + val溢出)
                if (minDist[from] == Integer.MAX_VALUE) {
                    continue;
                }

                // 分两种情况处理:
                // 情况1:前n-1轮(i < n):正常松弛,更新最短距离
                if (i < n) {
                    if (minDist[from] + val < minDist[to]) {
                        minDist[to] = minDist[from] + val;
                    }
                } 
                // 情况2:第n轮(i == n):检测是否还能松弛(判断负权环)
                else {
                    if (minDist[from] + val < minDist[to]) {
                        flag = true; // 能松弛 → 存在负权环
                    }
                }
            }
        }

        // 5. 结果输出
        if (flag) {
            // 存在负权环 → 输出"circle"
            System.out.println("circle");
        } else {
            // 无负权环 → 判断终点是否可达
            if (minDist[n] == Integer.MAX_VALUE) {
                // 终点n距离仍为无穷大 → 不可达
                System.out.println("unconnected");
            } else {
                // 输出起点1到终点n的最短距离
                System.out.println(minDist[n]);
            }
        }

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

4 bellman_ford之单源有限最短路

4.1 思路

限制松弛轮数:普通 Bellman-Ford 需 n-1 轮松弛(n 为节点数),这里仅松弛 k+1 轮(对应最多走 k 条边);

使用副本数组:每轮松弛前复制当前最短距离数组 minDist 到 minDist_copy,本轮所有松弛操作基于副本进行 ------ 保证每轮仅更新「走 i 条边」的最短距离,避免同轮内多次松弛导致边数超限;

结果判断:最终 minDist[dst] 若为无穷大则不可达,否则为最多 k 条边的最短距离。

4.2 实现代码

java 复制代码
import java.util.*;

public class Main {
    // 定义边的内部类:l=起点,r=终点,val=边的权重
    static class Edge {
        int l;    // 边的起点
        int r;    // 边的终点
        int val;  // 边的权重

        // 构造方法:初始化边的起点、终点、权重
        public Edge(int l, int r, int val) {
            this.l = l;
            this.r = r;
            this.val = val;
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // 1. 输入处理:节点数n、边数m
        int n = sc.nextInt(), m = sc.nextInt();

        // 2. 存储所有边:Bellman-Ford算法直接遍历所有边
        List<Edge> edges = new ArrayList<>();
        for (int i = 0; i < m; i++) {
            int l = sc.nextInt();  // 边的起点
            int r = sc.nextInt();  // 边的终点
            int val = sc.nextInt();// 边的权重
            edges.add(new Edge(l, r, val));
        }

        // 3. 输入关键参数:起点src、终点dst、最多经过k条边
        int src = sc.nextInt(), dst = sc.nextInt(), k = sc.nextInt();

        // 4. 初始化最短距离数组:minDist[i] 表示从src到i、最多走当前轮数条边的最短距离
        int[] minDist = new int[n + 1];
        Arrays.fill(minDist, Integer.MAX_VALUE); // 初始化为无穷大(不可达)
        minDist[src] = 0; // 起点到自身的距离为0(走0条边)

        // 5. 核心循环:最多走k条边 → 松弛k+1轮(轮数0对应走0条边,轮数k对应走k条边)
        for (int i = 0; i <= k; i++) {
            // 关键:复制当前距离数组作为本轮松弛的基准(避免同轮内多次松弛导致边数超限)
            int[] minDist_copy = Arrays.copyOf(minDist, n + 1);

            // 遍历所有边,执行松弛操作
            for (Edge cur : edges) {
                int l = cur.l;    // 当前边的起点
                int r = cur.r;    // 当前边的终点
                int val = cur.val;// 当前边的权重

                // 跳过起点不可达的情况(避免Integer.MAX_VALUE + val溢出)
                if (minDist_copy[l] == Integer.MAX_VALUE) {
                    continue;
                }

                // 松弛条件:通过当前边l→r,能得到更短的距离(且仅基于上一轮的结果)
                if (minDist_copy[l] + val < minDist[r]) {
                    minDist[r] = minDist_copy[l] + val;
                }
            }
        }

        // 6. 结果输出
        if (minDist[dst] == Integer.MAX_VALUE) {
            // 终点dst的距离仍为无穷大 → 最多走k条边时不可达
            System.out.println("unreachable");
        } else {
            // 输出最多走k条边时,src到dst的最短距离
            System.out.println(minDist[dst]);
        }

        sc.close(); // 关闭扫描器
    }
}
相关推荐
jing-ya2 小时前
day 55 图论part7
java·数据结构·算法·图论
技术净胜3 小时前
Gephi基于图论与物理模拟的网络可视化原理
数据分析·图论
落地加湿器4 小时前
Acwing算法课图论与搜索笔记
c++·笔记·算法·图论·dfs·bfs·图搜索算法
ccLianLian4 小时前
图论·二分图
图论
ccLianLian4 小时前
图论·图的存储
图论
I_LPL1 天前
day56 代码随想录算法训练营 图论专题9
算法·图论
jing-ya1 天前
day 54 图论part6
java·开发语言·数据结构·算法·图论
I_LPL2 天前
day54 代码随想录算法训练营 图论专题8
数据结构·图论·拓扑排序·dijkstra算法
jing-ya2 天前
day 53 图论part5
java·数据结构·算法·图论