Bellman_ford 算法每次松弛 都是对所有边进行松弛
但真正有效的松弛,是基于已经计算过的节点在做的松弛。
所以 Bellman_ford 算法 每次都是对所有边进行松弛,其实是多做了一些无用功。
只需要对 上一次松弛的时候更新过的节点作为出发节点所连接的边 进行松弛就够了。
基于以上思路,用队列来记录上次松弛的时候更新过的节点(其实用栈也行,对元素顺序没有要求)
用minDist数组来表达 起点到各个节点的最短距离
基于队列优化的bellman_ford的算法模拟过程:
94. 城市间货物运输 I
题目描述
某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。
网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。
请找出从城市 1 到城市 n 的所有可能路径中,综合政府补贴后的最低运输成本。如果最低运输成本是一个负数,它表示在遵循最优路径的情况下,运输过程中反而能够实现盈利。
城市 1 到城市 n 之间可能会出现没有路径的情况,同时保证道路网络中不存在任何负权回路。
输入描述
第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。
接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v (单向图)。
输出描述
如果能够从城市 1 到连通到城市 n, 请输出一个整数,表示运输成本。如果该整数是负数,则表示实现了盈利。如果从城市 1 没有路径可达城市 n,请输出 "unconnected"。
输入示例
6 7
5 6 -2
1 2 1
5 3 1
2 5 2
2 4 -3
4 6 4
1 3 5
输出示例
1
提示信息
示例中最佳路径是从 1 -> 2 -> 5 -> 6,路上的权值分别为 1 2 -2,最终的最低运输成本为 1 + 2 + (-2) = 1。
示例 2:
4 2
1 2 -1
3 4 -1
在此示例中,无法找到一条路径从 1 通往 4,所以此时应该输出 "unconnected"。
数据范围:
1 <= n <= 1000;
1 <= m <= 10000;
-100 <= v <= 100;
基于队列优化的bellman_ford的算法代码:
import java.util.*;
public class Main {
public static void main(String[] args) {
int n, m;
Scanner in = new Scanner(System.in);
n = in.nextInt();
m = in.nextInt();
List<Node>[] edges = new List[n + 1];
while (m-- > 0) {
int from = in.nextInt();
int to = in.nextInt();
int val = in.nextInt();
if (edges[from] == null) {
edges[from] = new LinkedList<>();
}
edges[from].add(new Node(to, val));
}
int[] minDest = new int[n + 1];
Arrays.fill(minDest, Integer.MAX_VALUE);
Queue<Integer> queue = new LinkedList();
minDest[1] = 0;
queue.add(1);
while (!queue.isEmpty()) {
Integer poll = queue.poll();
//更新minDest和队列
List<Node> edge = edges[poll];
if(edge==null){continue;}
for(Node temp:edge){
if(minDest[poll]!=Integer.MAX_VALUE&&minDest[poll]+temp.getVal()<minDest[temp.getTo()]){
minDest[temp.getTo()]=minDest[poll]+temp.getVal();
queue.add(temp.getTo());
}
}
}
String result= minDest[n]!=Integer.MAX_VALUE?minDest[n]+"":"unconnected";
System.out.println(result);
}
}
class Node {
int to;
int val;
public Node(int to, int val) {
this.to = to;
this.val = val;
}
public int getTo() {
return to;
}
public int getVal() {
return val;
}
}
while (!que.empty())
队里里 会不会造成死循环? 例如 图中有环,这样一直有元素加入到队列里?
其实有环的情况,要看它是 正权回路 还是 负权回路。
题目描述中,已经说了,本题没有 负权回路 。
但如果本题有 负权回路,那情况就不一样了,我在下一题目讲解中,会重点讲解 负权回路 带来的变化
bellman_ford之判断负权回路
本题是要我们判断 负权回路,也就是图中出现环且环上的边总权值为负数。
如果在这样的图中求最短路的话, 就会在这个环里无限循环 (也是负数+负数 只会越来越小),无法求出最短路径。
所以对于 在有负权值的图中求最短路,都需要先看看这个图里有没有负权回路。
在没有负权回路的图中,bellman_ford算法松弛 n 次以上 ,结果不会有变化。
有 负权回路,如果松弛 n 次,结果就会有变化了,因为 有负权回路 就是可以无限最短路径(一直绕圈,就可以一直得到无限小的最短距离)。
使用 队列优化版的bellman_ford(SPFA)
所有节点都与其他节点相连,每个节点的入度为 n-1 (n为节点数量),所以每个节点最多加入 n-1 次队列。
那么如果节点加入队列的次数 超过了 n-1次 ,那么该图就一定有负权回路。
95. 城市间货物运输 II
题目描述
某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。
网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。
然而,在评估从城市 1 到城市 n 的所有可能路径中综合政府补贴后的最低运输成本时,存在一种情况:图中可能出现负权回路。负权回路是指一系列道路的总权值为负,这样的回路使得通过反复经过回路中的道路,理论上可以无限地减少总成本或无限地增加总收益。为了避免货物运输商采用负权回路这种情况无限的赚取政府补贴,算法还需检测这种特殊情况。
请找出从城市 1 到城市 n 的所有可能路径中,综合政府补贴后的最低运输成本。同时能够检测并适当处理负权回路的存在。
城市 1 到城市 n 之间可能会出现没有路径的情况
输入描述
第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。
接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v。
输出描述
如果没有发现负权回路,则输出一个整数,表示从城市 1
到城市 n
的最低运输成本(包括政府补贴)。如果该整数是负数,则表示实现了盈利。如果发现了负权回路的存在,则输出 "circle"。如果从城市 1 无法到达城市 n,则输出 "unconnected"。
输入示例
4 4
1 2 -1
2 3 1
3 1 -1
3 4 1
输出示例
circle
提示信息
路径中存在负权回路,从 1 -> 2 -> 3 -> 1,总权值为 -1,理论上货物运输商可以在该回路无限循环赚取政府补贴,所以输出 "circle" 表示已经检测出了该种情况。
数据范围:
1 <= n <= 1000;
1 <= m <= 10000;
-100 <= v <= 100;
import java.util.*;
public class Main {
public static void main(String[] args) {
int n, m;
Scanner in = new Scanner(System.in);
n = in.nextInt();
m = in.nextInt();
List<Node>[] edges = new List[n + 1];
while (m-- > 0) {
int from = in.nextInt();
int to = in.nextInt();
int val = in.nextInt();
if (edges[from] == null) {
edges[from] = new LinkedList<>();
}
edges[from].add(new Node(to, val));
}
int[] minDest = new int[n + 1];
int[] count = new int[n + 1];
Arrays.fill(minDest, Integer.MAX_VALUE);
Queue<Integer> queue = new LinkedList();
minDest[1] = 0;
queue.add(1);
count[1]++;
while (!queue.isEmpty()) {
Integer poll = queue.poll();
//更新minDest和队列
List<Node> edge = edges[poll];
if (edge == null) {
continue;
}
for (Node temp : edge) {
if (minDest[poll] != Integer.MAX_VALUE && minDest[poll] + temp.getVal() < minDest[temp.getTo()]) {
minDest[temp.getTo()] = minDest[poll] + temp.getVal();
queue.add(temp.getTo());
count[temp.getTo()]++;
if(count[temp.getTo()]>=n){//如果加入队列次数超过 n-1次 就说明该图与负权回路
System.out.println("circle");
return;
}
}
}
}
String result = minDest[n] != Integer.MAX_VALUE ? minDest[n] + "" : "unconnected";
System.out.println(result);
}
}
class Node {
int to;
int val;
public Node(int to, int val) {
this.to = to;
this.val = val;
}
public int getTo() {
return to;
}
public int getVal() {
return val;
}
}
bellman_ford之单源有限最短路
题目:
96. 城市间货物运输 III
题目描述
某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。
网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。
请计算在最多经过 k 个城市的条件下,从城市 src 到城市 dst 的最低运输成本。
输入描述
第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。
接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v。
最后一行包含三个正整数,src、dst、和 k,src 和 dst 为城市编号,从 src 到 dst 经过的城市数量限制。
输出描述
输出一个整数,表示从城市 src 到城市 dst 的最低运输成本,如果无法在给定经过城市数量限制下找到从 src 到 dst 的路径,则输出 "unreachable",表示不存在符合条件的运输方案。
输入示例
6 7
1 2 1
2 4 -3
2 5 2
1 3 5
3 5 1
4 6 4
5 6 -2
2 6 1
输出示例
0
提示信息
从 2 -> 5 -> 6 中转一站,运输成本为 0。
1 <= n <= 1000;
1 <= m <= 10000;
-100 <= v <= 100;
思路:
题目中描述是 最多经过 k 个城市的条件下,而不是一定经过k个城市,也可以经过的城市数量比k小,但要最短的路径。
从图中看:从2到6经过一站的最佳路线是 2->5->6, 最低成本为0;
在bellman_ford算法中,节点数量为n,起点1到终点n,最多是 n-1 条边相连。 那么对所有边松弛 n-1 次 就一定能得到 起点到达 终点的最短距离。
故从src 到dest经过k站,则对所有边松弛k+1次就一定能得到单源有限最短路径
代码版本1:
import java.util.*;
public class Main {
public static void main(String[] args) {
int n, l;
Scanner in = new Scanner(System.in);
n = in.nextInt();
l = in.nextInt();
List<Node> edges= new LinkedList<>();
while (l-- > 0) {
int x = in.nextInt();
int y = in.nextInt();
int val = in.nextInt();
edges.add(new Node(x,y,val));
}
//初始化
int start = in.nextInt();
int end = in.nextInt();
int k=in.nextInt();
int[] minDest = new int[n + 1];
Arrays.fill(minDest,Integer.MAX_VALUE);
minDest[start] = 0;
//每条边处理n-1次
for(int i=1;i<=k+1;i++){
//取出每边
for(Node node:edges){
int from=node.getFrom();
int to=node.getTo();
int val=node.getVal();
if(minDest[from]!=Integer.MAX_VALUE&& minDest[from]+val<minDest[to]){
minDest[to]=minDest[from]+val;
}
}
}
String result = minDest[end] == Integer.MAX_VALUE ? "unconnected" : minDest[end] + "";
System.out.println(result);
}
}
class Node {
int from;
int to;
int val;
public int getFrom() {
return from;
}
public void setFrom(int from) {
this.from = from;
}
public int getTo() {
return to;
}
public Node(int from, int to, int val) {
this.from = from;
this.to = to;
this.val = val;
}
public void setTo(int to) {
this.to = to;
}
public int getVal() {
return val;
}
public void setVal(int val) {
this.val = val;
}
}
版本1的代码没通过
图中 1->4成本最低路径 1->2->3->1->4 ,最低成本为1
为什么会出现-2的情况?打印minDest数组看看!!!
注意:如果示例是有负权回路的
对所有边松弛第一次的过程中,大家会发现,不仅仅 与起点一条边相连的节点更新了,所有节点都更新了。
这样就造成了一个情况,即:计算minDist数组的时候,基于了本次松弛的 minDist数值,而不是上一次 松弛时候minDist的数值。
所以在每次计算 minDist 时候,要基于 对所有边上一次松弛的 minDist 数值才行,所以我们要记录上一次松弛的minDist。
代码版本二:
import java.util.*;
public class Main {
public static void main(String[] args) {
int n, l;
Scanner in = new Scanner(System.in);
n = in.nextInt();
l = in.nextInt();
List<Node> edges= new LinkedList<>();
while (l-- > 0) {
int x = in.nextInt();
int y = in.nextInt();
int val = in.nextInt();
edges.add(new Node(x,y,val));
}
//初始化
int start = in.nextInt();
int end = in.nextInt();
int k=in.nextInt();
int[] minDest = new int[n + 1];
int[] copy_minDest = Arrays.copyOf(minDest, minDest.length);
Arrays.fill(minDest,Integer.MAX_VALUE);
minDest[start] = 0;
//每条边处理n-1次
for(int i=1;i<=k+1;i++){
copy_minDest=Arrays.copyOf(minDest,minDest.length);
//取出每边
for(Node node:edges){
int from=node.getFrom();
int to=node.getTo();
int val=node.getVal();
注意使用copy_minDest 来计算 minDist
if(copy_minDest[from]!=Integer.MAX_VALUE&& copy_minDest[from]+val<minDest[to]){
minDest[to]=copy_minDest[from]+val;
}
}
}
String result = minDest[end] == Integer.MAX_VALUE ? "unreachable" : minDest[end] + "";
System.out.println(result);
}
}
class Node {
int from;
int to;
int val;
public int getFrom() {
return from;
}
public void setFrom(int from) {
this.from = from;
}
public int getTo() {
return to;
}
public Node(int from, int to, int val) {
this.from = from;
this.to = to;
this.val = val;
}
public void setTo(int to) {
this.to = to;
}
public int getVal() {
return val;
}
public void setVal(int val) {
this.val = val;
}
}