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
数组是否发生变化。
代码
import java.util.*;
class Edge {
int to;
int val;
public Edge(int to, int val) {
this.to = to;
this.val = val;
}
}
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
List<List<Edge>> grid = new ArrayList<>(n + 1);
for (int i = 0; i < n + 1; i++) {
grid.add(new LinkedList<>());
}
int s, e, v;
for (int i = 0; i < m; i++) {
s = scanner.nextInt();
e = scanner.nextInt();
v = scanner.nextInt();
List<Edge> edges = grid.get(s);
edges.add(new Edge(e, v));
}
int[] minDist = new int[n + 1];
Arrays.fill(minDist, Integer.MAX_VALUE);
// 记录节点加入队列几次
int [] visited = new int[n + 1];
Queue<Integer> queue = new LinkedList<>();
queue.offer(1);
visited[1]++;
// 起始点到自身的距离为0
minDist[1] = 0;
while (!queue.isEmpty()) {
int cur = queue.poll();
for (Edge edge : grid.get(cur)) {
int from = cur;
int to = edge.to;
int value = edge.val;
if (minDist[to] > minDist[from] + value) {
minDist[to] = minDist[from] + value;
queue.offer(to);
visited[to]++;
if(visited[to] == n){
// 如果加入队列次数超过 n-1次 就说明该图与负权回路
System.out.print("circle");
return;
}
}
}
}
// 不能到达终点
if (Integer.MAX_VALUE == minDist[n]) {
System.out.print("unconnected");
} else {
// 到达终点最短路径
System.out.print(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次就好。
代码
import java.util.*;
class Edge {
int to;
int val;
public Edge(int to, int val) {
this.to = to;
this.val = val;
}
}
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
List<List<Edge>> grid = new ArrayList<>(n + 1);
for (int i = 0; i < n + 1; i++) {
grid.add(new LinkedList<>());
}
int s, e, v;
for (int i = 0; i < m; i++) {
s = scanner.nextInt();
e = scanner.nextInt();
v = scanner.nextInt();
List<Edge> edges = grid.get(s);
edges.add(new Edge(e, v));
}
int src = scanner.nextInt();
int dst = scanner.nextInt();
int k = scanner.nextInt();
k++;
int[] minDist = new int[n + 1];
Arrays.fill(minDist, Integer.MAX_VALUE);
Queue<Integer> queue = new LinkedList<>();
queue.offer(src);
minDist[src] = 0;
// 松弛 k + 1 轮
while (k-- > 0 && !queue.isEmpty()) {
boolean [] visited = new boolean[n + 1];
int [] tmp = minDist.clone(); // 深拷贝 获取上一次计算的结果
int size = queue.size(); // 记录上次入队列的节点个数
while (size-- > 0) { // 上一轮松弛入队列的节点,这次对应的边都要做松弛
int cur = queue.poll();
for (Edge edge : grid.get(cur)) {
int from = cur;
int to = edge.to;
int value = edge.val;
if (minDist[to] > tmp[from] + value) {
minDist[to] = tmp[from] + value;
if(visited[to]) continue;
visited[to] = true;
queue.offer(to);
}
}
}
}
if (Integer.MAX_VALUE == minDist[dst]) {
System.out.print("unreachable");
} else {
System.out.print(minDist[dst]);
}
}
}