Bellman_ford队列优化算法(SPFA算法)
Bellman_ford算法每次松弛都是对所有边进行松弛,但真正有效的松弛,是基于已经计算过的节点做的松弛。这句话具体看代码随想录的例子代码随想录 (programmercarl.com)
在部分节点未计算前,对其进行松弛是无意义的因此只需对上一次松弛时更新过的节点作为出发节点所连接的边进行松弛即可。
利用队列记录上次松弛的时候更新过的节点。同样我们使用minDist数组,每次松弛过后,对minDist数组中改变了的节点将其存入队列中,需注意,每个节点只需加入队列一次,所以需要一个visited数组来记录入队过的元素,已经入队的元素不再重复入队。
代码如下:
#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;
cin>>n>>m; // 输入顶点数n和边数m
// 创建一个邻接表,用于存储图
vector<list<Edge>>grid(n+1);
int p1,p2,val;
// 读入所有边的信息,并构建图
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>min_Dist(n+1,INT_MAX);
min_Dist[start]=0; // 起点到自己的距离为0
// 初始化访问数组,所有顶点初始状态为未访问
//vector<int>visited(n+1,0);
//visited[start]=1; // 起点标记为已访问
queue<int>que;
que.push(start); // 将起点加入队列
// 开始BFS
while(!que.empty()){
int node = que.front(); // 获取队首元素
que.pop(); // 弹出队首元素
// 遍历当前节点的所有邻接边
for(auto edge: grid[node]){
int from = node; // 当前节点
int to = edge.to; // 邻接边的终点
int val = edge.val; // 邻接边的权重
// 如果通过当前节点到达邻接边的终点的距离更短,则更新距离
if(min_Dist[to] > min_Dist[from] + val){
min_Dist[to] = min_Dist[from] + val;
que.push(to); // 将终点加入队列
// 注释掉的代码:由于已经更新了距离,所以不需要再次检查是否访问过
// if(visited[to]==0){
// visited[to] = 1;
// que.push(to);
//}
}
}
}
// 输出结果
if(min_Dist[end] == INT_MAX)
cout<<"unconnected"<<endl; // 如果终点不可达,则输出"unconnected"
else
cout<<min_Dist[end]<<endl; // 否则输出起点到终点的最短距离
}
Bellman_ford判断负权回路
95. 城市间货物运输 II (kamacoder.com)
本题在计算最短路径的基础上,还要判断负权回路,否在负权回路的环会不可避免地出现负无穷。
之前有提过,Bellman_ford进行n-1次松弛能够得到负权的最短路径,若无环,n-1次、n次
n+1次松弛的结果不会发生变化,而若存在负权回路,则n-1次以上的最短路径会不断变小。
这样,本题的代码如下 代码随想录 (programmercarl.com)
#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;
// 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之单源有限最短路
96. 城市间货物运输 III (kamacoder.com)
具体参考代码随想录
bellman_ford之单源有限最短路 | 代码随想录 (programmercarl.com)
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
int main() {
// 读取节点数n和边数m
int n, m;
cin >> n >> m;
// 定义一个二维向量grid来存储图的所有边
vector<vector<int>> grid;
// 读取每条边的信息,包括起点s,终点t和权重v
int s, t, v;
for (int i = 0; i < m; i++) {
cin >> s >> t >> v;
grid.push_back({s, t, v}); // 将边的信息添加到grid中
}
// 读取源点src,目标点dst和最大转机次数k
int src, dst, k;
cin >> src >> dst >> k;
// 定义一个向量minDist来存储从源点到每个节点的最短距离,初始值设为INT_MAX
vector<int> minDist(n + 1, INT_MAX);
minDist[src] = 0; // 源点到自身的距离为0
// 定义一个向量minDist_copy来记录上一次遍历的结果
vector<int> minDist_copy(n + 1);
// 进行k+1次遍历,因为需要考虑转机k次的情况
for (int i = 1; i <= k + 1; i++) {
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; // 更新最短距离
}
}
}
// 输出结果,如果目标点不可达,输出"unreachable"
if (minDist[dst] == INT_MAX)
cout << "unreachable" << endl;
else
cout << minDist[dst] << endl; // 输出从源点到目标点的最短距离
}
- 时间复杂度: O(K * E) , K为至多经过K个节点,E为图中边的数量
- 空间复杂度: O(N) ,即 minDist 数组所开辟的空间