This commit is contained in:
programmercarl
2024-05-26 10:02:54 +08:00
parent 259d197e9e
commit 4fa3e4d9b0
11 changed files with 1561 additions and 25 deletions

View File

@@ -8,11 +8,14 @@
某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。
网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。
网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。
权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。
请找出从城市 1 到城市 n 的所有可能路径中,综合政府补贴后的最低运输成本。如果最低运输成本是一个负数,它表示在遵循最优路径的情况下,运输过程中反而能够实现盈利。
请找出从城市 1 到城市 n 的所有可能路径中,综合政府补贴后的最低运输成本。
如果最低运输成本是一个负数,它表示在遵循最优路径的情况下,运输过程中反而能够实现盈利。
城市 1 到城市 n 之间可能会出现没有路径的情况,同时保证道路网络中不存在任何负权回路。
@@ -41,11 +44,11 @@
1 3 5
```
## 思路
## 背景
本题我们来系统讲解 Bellman_ford 队列优化算法 也叫SPFA算法Shortest Path Faster Algorithm
> SPFA的称呼来自 1994年西南交通大学段凡丁的论文其实Bellman_ford 提出后不久 20世纪50年代末期 就有队列优化的版本,国际上不承认这个算法是是国内提出的。 所以国际上一般称呼 算法为 Bellman_ford 队列优化算法Queue improved Bellman-Ford
> SPFA的称呼来自 1994年西南交通大学段凡丁的论文其实Bellman_ford 提出后不久 20世纪50年代末期 就有队列优化的版本,国际上不承认这个算法是是国内提出的。 所以国际上一般称呼 算法为 Bellman_ford 队列优化算法Queue improved Bellman-Ford
大家知道以上来历,知道 SPFA 和 Bellman_ford 队列优化算法 指的都是一个算法就好。
@@ -72,6 +75,8 @@
用队列来记录。(其实用栈也行,对元素顺序没有要求)
## 模拟过程
接下来来举例这个队列是如何工作的。
以示例给出的所有边为例:
@@ -88,19 +93,19 @@
我们依然使用**minDist数组来表达 起点到各个节点的最短距离**例如minDist[3] = 5 表示起点到达节点3 的最小距离为5
初始化起点为节点1 起点到起点的最短距离为0所以minDist[1] 为 0。 将节点1 加入队列 (下次松弛节点1开始
初始化起点为节点1 起点到起点的最短距离为0所以minDist[1] 为 0。 将节点1 加入队列 (下次松弛节点1开始
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240411115555.png)
------------
从队列里取出节点1松弛节点1 作为出发点接的边节点1 -> 节点2和边节点1 -> 节点3
从队列里取出节点1松弛节点1 作为出发点接的边节点1 -> 节点2和边节点1 -> 节点3
节点1 -> 节点2权值为1 minDist[2] > minDist[1] + 1 ,更新 minDist[2] = minDist[1] + 1 = 0 + 1 = 1 。
节点1 -> 节点3权值为5 minDist[3] > minDist[1] + 5更新 minDist[3] = minDist[1] + 5 = 0 + 5 = 5。
将节点2节点3 加入队列,如图:
将节点2节点3 加入队列,如图:
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240411115544.png)
@@ -108,7 +113,7 @@
-----------------
从队列里取出节点2松弛节点2 作为出发点接的边节点2 -> 节点4和边节点2 -> 节点5
从队列里取出节点2松弛节点2 作为出发点接的边节点2 -> 节点4和边节点2 -> 节点5
节点2 -> 节点4权值为1 minDist[4] > minDist[2] + (-3) ,更新 minDist[4] = minDist[2] + (-3) = 1 + (-3) = -2 。
@@ -123,7 +128,7 @@
--------------------
从队列里出去节点3松弛节点3 作为出发点接的边。
从队列里出去节点3松弛节点3 作为出发点接的边。
因为没有从节点3作为出发点的边所以这里就从队列里取出节点3就好不用做其他操作如图
@@ -132,11 +137,11 @@
------------
从队列中取出节点4松弛节点4作为出发点接的边节点4 -> 节点6
从队列中取出节点4松弛节点4作为出发点接的边节点4 -> 节点6
节点4 -> 节点6权值为4 minDist[6] > minDist[4] + 4更新 minDist[6] = minDist[4] + 4 = -2 + 4 = 2 。
节点6加入队列
节点6加入队列
如图:
@@ -145,7 +150,7 @@
---------------
从队列中取出节点5松弛节点5作为出发点接的边节点5 -> 节点3节点5 -> 节点6
从队列中取出节点5松弛节点5作为出发点接的边节点5 -> 节点3节点5 -> 节点6
节点5 -> 节点3权值为1 minDist[3] > minDist[5] + 1 ,更新 minDist[3] = minDist[5] + 1 = 3 + 1 = 4
@@ -157,14 +162,14 @@
因为节点3和 节点6 都曾经加入过队列,不用重复加入,避免重复计算。
因为节点3 和 节点6 都曾经加入过队列,不用重复加入,避免重复计算。
在代码中我们可以用一个数组 visited 来记录入过队列的元素,加入过队列的元素,不再重复入队列。
--------------
从队列中取出节点6松弛节点6 作为出发点接的边。
从队列中取出节点6松弛节点6 作为出发点接的边。
节点6作为终点没有可以出发的边。
@@ -181,7 +186,7 @@
了解了大体流程,我们再看代码应该怎么写。
在上面模拟过程中,我们每次都要知道 一个节点作为出发点接了哪些节点。
在上面模拟过程中,我们每次都要知道 一个节点作为出发点接了哪些节点。
如果想方便知道这些数据,就需要使用邻接表来存储这个图,如果对于邻接表不了解的话,可以看 [kama0047.参会dijkstra堆](./kama0047.参会dijkstra堆.md) 中 图的存储 部分。
@@ -279,7 +284,7 @@ n为其他数值的时候也是一样的。
并没有计算 出队列 和 入队列的时间消耗。 因为这个在不同语言上 时间消耗也是不一定的。
以C++为例,以下两代码理论上,时间复杂度都是 O(n)
以C++为例,以下两代码理论上,时间复杂度都是 O(n)
```CPP
for (long long i = 0; i < n; i++) {
@@ -316,7 +321,7 @@ SPFA队列优化版Bellman_ford 在理论上 时间复杂度更胜一筹
这里可能有录友疑惑,`while (!que.empty())` 队里里 会不会造成死循环? 例如 图中有环,这样一直有元素加入到队列里?
其实有环的情况,要看它是 正权回路 还是 负回路。
其实有环的情况,要看它是 正权回路 还是 负回路。
题目描述中,已经说了,本题没有 负权回路 。