• 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;
    }
    
    你以为的极限,也许只是别人的起点
  • 相关阅读:
    mybatis plus 执行sql
    springboot 打印 mybatisplus 的sql
    easyexcel 简单使用
    springboot ehcache
    easyexcel 时间转换
    springboot stopwatch
    c# 开机自动启动
    sqlHelper
    DirectX录音
    c# 发邮件
  • 原文地址:https://www.cnblogs.com/LengDing/p/14299465.html
Copyright © 2020-2023  润新知