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]);
}
}
1123

被折叠的 条评论
为什么被折叠?



