SPFA算法
由BellmanFord算法进行优化得来的
当BellmanFord算法 遍历所有边的时候 dis[b]=min[dis[b],dis[a]+c];
这个表达式只有dis[a]变小的时候才可能变小。
SPFA 既可以判断负环也可以判断无负环的
所以SPFA将dis[a]变小的点加入队列,并更新他们所连接的边,可以省去无用的迭代
核心操作
1.先将1点入队
2.将dis[a]变小的节点放入队列中
3.取出队头->t 把队头删掉 用t更新以t为起点的边
4.更新成功后把b加入队列,判断,如果队列中有b则不加入
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
const int M = 1e5 + 10;
int n, m, h[N], e[M], ne[M], wei[M], vis[N], dis[N], idx;
void add(int a, int b, int c) {
e[idx] = b;
wei[idx] = c; //有权重时,用邻接表存储图,在图中加入一条边
ne[idx] = h[a];
h[a] = idx++;
}
int SPFA() {
memset(dis, 0x3f, sizeof(dis));//初始化距离
dis[1] = 0;
queue<int> q; //定义队列 SPFA优化 队列中存储的是边权重减小的边 只有前驱减小了,他的后继才有可能减小
q.push(1);
vis[1] = 1;
while (q.size()) {
int now = q.front();
q.pop();
vis[now] = false;// 由于SPFA中存在负权边 所有可重复加入 此初与Dijkstra不同,由于Dijkstra 所有边权为正,所以最短路不会经过一个点两次
for (int i = h[now]; i != -1; i=ne[i]) {//核心操作,在边权重减小的边中,更新他的后继变小的边,
int j = e[i];
if (dis[j] > dis[now] + wei[i]) {
dis[j] = dis[now] + wei[i];
if (!vis[j]) {//如果这个点被更新了且不在队列中,则把他加入队列
q.push(j);
}
}
}
}
if (dis[n] == 0x3f3f3f3f)// 当队列为空时,SPFA算法已经帮我们找到了到n点的最短路径
return -1;
else return dis[n];
}
int main()
{
memset(h, -1, sizeof(h));
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
if (SPFA() == -1) {
cout << "impossible" << endl;
}
else cout << SPFA() << endl;
return 0;
}
SPFA判负环
dis[x] : 表示当前 1~x的最短距离
cnt[x]: 表示当前最短路的边数
更新
dis[x]=dis[t]+w[i];
cnt[x]=cnt[t]+1; //从1x的边数=从1t的边数+1
如果cnt[x]>=n 意味着从1 到x 至少经过了n条边 ->至少经过了n+1个点->则存在环
因为每次更新路径都是在减少,所以环一定是负环
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
const int M = N * 2;
int n, m, h[N], e[M], ne[M], wei[M], vis[N], dis[N], cnt[N], idx;
//dis[N]代表到这个点的距离 cnt[N] 代表走到这个点经历了多少边
void add(int a, int b, int c) {
e[idx] = b;
wei[idx] = c;
ne[idx] = h[a];
h[a] = idx++;
}
bool SPFA_Negative() {
queue<int> q;
for (int i = 1; i <= n; i++) {
q.push(i);
vis[1] = 1; //从1号点可能走不到负环 所以从多个起点开始
}
while (q.size()) {
int now = q.front();
q.pop();
vis[now] = 0;
for (int i = h[now]; i != -1; i = ne[i]) {
int j = e[i];
if (dis[j] > dis[now] + wei[i]) {
dis[j] = dis[now] + wei[i];
cnt[j] = cnt[now] + 1;//更新边的距离的时候把cnt也更新
if (cnt[j] >= n)
return true;//如果到某个点经历了大于n个点 则说明存在自环 因为距离都是往短了更新 所以存在负环
if (!vis[j]) {
q.push(j);
vis[j] = 1;
}
}
}
}
return false;
}
int main()
{
memset(h, -1, sizeof(h));
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
if (SPFA_Negative())
cout << "Yes" << endl;
else cout << "No" << endl;
return 0;
}