SPFA算法

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;
}
你以为的极限,也许只是别人的起点
原文地址:https://www.cnblogs.com/LengDing/p/14299465.html