Bellman_ford和spfa算法
-
bellman_ford算法在要求起点到终点存在负权边,要求在指定k步(这是spfa无法替代的)
-
bellman_ford和spfa都可以判断图中有无负权环
🌕实现

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510,M=10010;
//边结构体
typedef struct{
int from,to,weight;
}Edge;
//M条边
Edge edge[M];
int n,m,k;
int dist[N];
//记录上一次被松弛的边的情况
int backup[N];
void bellman_ford(){
memset(dist,0x3f,sizeof(dist));
dist[1]=0;
//这里就类似宽搜的思想
for(int i=0;i<k;i++){
//每次迭代都是在上一次的基础上进行的,因此我们在代码实现时要注意保留上一次的结果
//用上一次松弛过的dist进行本次松弛
memcpy(backup,dist,sizeof(dist));
for(int j=0;j<m;j++){
auto e =edge[j];
//松弛该边
// if(dist[e.to]>backup[e.from]+e.weight){
// dist[e.to]==backup[e.from]+e.weight;
// }
//这里要用上一次已经松弛过的边来扩充下一层
dist[e.to]=min(dist[e.to],backup[e.from]+e.weight);
}
}
}
int main()
{
cin>>n>>m>>k;
int from,to,weight;
for(int i=0;i<m;i++){
cin>>from>>to>>weight;
edge[i]={from,to,weight};
}
bellman_ford();
if(dist[n] > 0x3f3f3f3f/2) cout<<"impossible"<<endl;
else cout<<dist[n]<<endl;
return 0;
}
-
可见Bellman_ford算法是通过n次循环来扩充边的,并且每次还是基于上次边更新的成果,且由于它会把之前也已有的边全部更新(导致串联),所以要使用backup数组记录上一次更新的结果
SPFA
由于Bellman_ford通过n次循环来扩充边,很想宽搜,所以SPFA就可以采用宽搜来优化

#include<iostream> #include<cstring> #include<algorithm> #include<queue> //其实spfa很简单,就是把bellman_ford那个傻傻的全面循环来一层层扩充图(很像宽搜)->变成宽搜 //spfa会长得很像dij 堆版,这里是用队列来实现宽搜 using namespace std; const int N = 1e5+10; int n,m; int h[N], e[N], w[N], ne[N], idx; int q[N], dist[N]; //st[i],判断i是否已经再队列中了 bool st[N]; void add(int a, int b, int c) // 添加一条边a->b,边权为c { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ; } int spfa(){ memset(dist,0x3f,sizeof dist); dist[1]=0; queue<int> q; q.push(1); st[1]=true; while(q.size()){ int t =q.front(); q.pop(); st[t]=false; for(int i=h[t];i!=-1;i=ne[i]){ int j=e[i]; if(dist[j]>dist[t]+w[i]){ dist[j]=dist[t]+w[i]; //不在队列中,可能a可以更新c,b也能更新c且b更新c更小,不需要重复往队列中插入 if(!st[j]){ q.push(j); st[j]=true; } } } } return dist[n]; } int main() { cin>>n>>m; memset(h,-1,sizeof h); while(m--){ int a,b,c; cin>>a>>b>>c; add(a,b,c); } int ans=spfa(); if(ans== 0x3f3f3f3f) puts("impossible"); else cout<<ans<<endl; return 0; } -
SPFA求负环
n点m边的图,设dist[x]为终点为x,起点任意点的最短距离要经过的边数,如果dist[x]>=n 那么可以推出
无负权环情况下有n+1->存在负权环

#include <cstring> #include <iostream> #include <algorithm> #include <queue> using namespace std; const int N = 2010, M = 10010; int n, m; int h[N], w[M], e[M], ne[M], idx; //这里dist[i]值得是 起点为任意,终点为i,仅用来更新负权边 int dist[N], cnt[N]; bool st[N]; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ; } bool spfa() { queue<int> q; for (int i = 1; i <= n; i ++ ) { st[i] = true; q.push(i); } while (q.size()) { int t = q.front(); q.pop(); st[t] = false; for (int i = h[t]; i != -1; i = ne[i]) { int j = e[i]; //更新负权边,能构成负权环的会一致在里面打转, //不是负权环的会都出队列,负权环那几个点会进来出去进来 if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; cnt[j] = cnt[t] + 1; if (cnt[j] >= n) return true; if (!st[j]) { q.push(j); st[j] = true; } } } } return false; } int main() { scanf("%d%d", &n, &m); memset(h, -1, sizeof h); while (m -- ) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c); } if (spfa()) puts("Yes"); else puts("No"); return 0; }

浙公网安备 33010602011771号