代码随想录算法训练营|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]); // 输出最小距离
}
}
原文地址:https://blog.csdn.net/weixin_43903745/article/details/140658523
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!