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(); // 关闭扫描器
}
}