文章链接:Bellman_ford 队列优化算法(又名SPFA)、bellman_ford之判断负权回路、bellman_ford之单源有限最短路
Bellman_ford 队列优化算法(又名SPFA)
优化思路:
只需要对上一次松弛的时候更新过的节点作为出发节点所连接的边进行松弛就够了。
B站视频讲解:【最短路径算法全套(floyed+dijstra+Bellman+SPFA)】https://www.bilibili.com/video/BV1RK4y1d7ct?p=4&vd_source=65a224d3f97ae5a1002c0964faf8a876
思路:
若该点更新了minDist数组,那么就加入队列;
从队列中一次取出点,计算从起点到达该点的距离,若小于minDist数组,就更新,然后出队,若大于,就直接出队;
循环操作,直至队列为空。
#include <iostream>
#include <vector>
#include <queue>
#include <list>
#include <climits>
using namespace std;
struct Edge { //邻接表
int to; // 链接的节点
int val; // 边的权重
Edge(int t, int w): to(t), val(w) {} // 构造函数
};
int main() {
int n, m, p1, p2, val;
cin >> n >> m;
vector<list<Edge>> grid(n + 1);
vector<bool> isInQueue(n + 1);
for (int i = 0; i < m; i++) {
cin >> p1 >> p2 >> val;
grid[p1].push_back(Edge(p2, val));
}
int start = 1;
int end = n;
vector<int> minDist(n + 1, INT_MAX); // minDist表示起点到各个节点的最短距离
minDist[start] = 0;
queue<int> que;
que.push(start);
while (!que.empty()) {
int node = que.front(); // 取出节点
que.pop(); // 记得删除
isInQueue[node] = false; // 从队列中取出,要取消标记
for (Edge edge : grid[node]) { // 循环node指向的点
int from = node; // 节点
int to = edge.to; // 节点指向的点
int value = edge.val; // 边的权值
if (minDist[to] > minDist[from] + value) { // 开始松弛
minDist[to] = minDist[from] + value;
if (isInQueue[to] == false) { // 不在队列里才要加入队列
que.push(to);
isInQueue[to] = true; // 标记
}
}
}
}
if (minDist[end] == INT_MAX) {
cout << "unconnected" << '\n';
} else {
cout << minDist[end] << '\n';
}
}
bellman_ford之判断负权回路
思路:
本题与上一题的区别:需要判断是否有负权回路,也就是图中出现环且环上的边总权值为负数。
(如果在这样的图中求最短路的话, 就会在这个环里无限循环 (也是负数+负数 只会越来越小),无法求出最短路径)
(注意:一般对于在有负权值的图中求最短路,都需要先看看这个图里有没有负权回路。)
如何判断:根据最短路的算法,对于有负值的我们需要使用Bellman_ford算法进行松弛操作,松弛 n-1 次所有的边就可以求得起点到任何节点的最短路径,松弛 n 次以上,minDist数组(记录起到到其他节点的最短距离)中的结果也不会有改变。那么解决本题的核心思路,就是在 kama94.城市间货物运输I 的基础上,再多松弛一次,看minDist数组是否发生变化。
#include <iostream>
#include <vector>
#include <list>
#include <climits>
using namespace std;
int main() {
int n, m, p1, p2, val;
cin >> n >> m;
vector<vector<int>> grid;
for(int i = 0; i < m; i++){
cin >> p1 >> p2 >> val;
grid.push_back({p1, p2, val});
}
int start = 1;
int end = n;
vector<int> minDist(n + 1 , INT_MAX);
minDist[start] = 0;
bool flag = false;
for (int i = 1; i <= n; i++) {
// 松弛n次,最后一次判断负权回路
for (vector<int> &side : grid) {
int from = side[0];
int to = side[1];
int price = side[2];
if (i < n) {
if (minDist[from] != INT_MAX && minDist[to] > minDist[from] + price) minDist[to] = minDist[from] + price;
} else { // 判断负权回路
if (minDist[from] != INT_MAX && minDist[to] > minDist[from] + price) flag = true;
}
}
}
if (flag) cout << "circle" << endl;
else if (minDist[end] == INT_MAX) {
cout << "unconnected" << endl;
} else {
cout << minDist[end] << endl;
}
}
bellman_ford之单源有限最短路
思路:
不能直接套用以上代码并松弛k + 1次就行:
计算minDist数组的时候,有时候是基于本次松弛的 minDist数值,而不是上一次松弛时候minDist的数值。
所以在每次计算 minDist 时候,要基于对所有边上一次松弛的 minDist 数值才行,所以我们要记录上一次松弛的minDist。
#include <iostream>
#include <vector>
#include <list>
#include <climits>
using namespace std;
int main() {
int src, dst, k, p1, p2, val, m, n;
cin >> n >> m;
vector<vector<int>> grid; // 矩阵
for (int i = 0; i < m; i++) {
cin >> p1 >> p2 >> val;
grid.push_back({p1, p2, val});
}
cin >> src >> dst >> k;
vector<int> minDist(n + 1, INT_MAX);
minDist[src] = 0;
vector<int> minDist_copy(n + 1); // 用于记录上一次遍历的结果
for (int i = 1; i <= k + 1; i++) { // k个城市,k + 1条边
minDist_copy = minDist; // 用于记录上一次遍历的结果
for (vector<int> &side : grid) {
int from = side[0];
int to = side[1];
int price = side[2];
if (minDist_copy[from] != INT_MAX && minDist[to] > minDist_copy[from] + price) {
minDist[to] = minDist_copy[from] + price;
}
}
}
if (minDist[dst] == INT_MAX) cout << "unreachable" << endl; // 不能到达终点
else cout << minDist[dst] << endl; // 到达终点最短路径
}