zoukankan      html  css  js  c++  java
  • 图论学习笔记——SPFA判断负环

    算法描述

    有一个n个点、m条边的有向/无向有权图,判断该图中有没有负环。

    注意:图并不一定所有点都是联通的

    负环的定义:图中形成了一个环,且环上面的边权之和为负数。

    例题:AcWing 852. spfa判断负环

    分析与解法

    负环是在写最短路(尤其是 SPFA)的问题中需要考虑的问题,它会导致程序陷入死循环,程序里需要避免这个问题。

    因为出现了负数,所以 Dijkstra 算法可以排除了,于是转向效率略低但可以处理负数的 SPFA。

    在SPFA算法中,遇见了负环会导致最短路的值会不断减小。有一些点会不断更新入队,队列永远不为空,可以从这里找到突破口。

    不难想到可以增加一个统计每个结点入队次数的数组,如果一个点入队超过了 (n) 次(也就是连正常情况下最多的入队次数都超过了),说明有一个点被重复使用,就判定有负环。

    还有一种方法:统计某一个点到该点的最短路目前包含多少条边,每次满足三角行不等式时更新这个值。如果一条最短路上包含了超过 (n - 1) 条边,说明有一条边被重复使用,有负环。

    但这两个思路都有一个缺陷:由于图并不保证两点之间一定能到达,如果从任意一点向任意一点的最短路中没有出现负环(就像以下这个情况),程序就会出错:

    如图,如果求的是1到其他点的最短路,则不会出现负环,会报错。

    解法1:

    从每个点跑一次SPFA,这样肯定能找出负环。

    一般复杂度 (O(NM)) ,最差复杂度 (O(N^2M)),难以接受。

    解法2:

    可以再建立一个 (0) 号结点,我们称它为“虚拟源点”。

    把它向所有节点连一条边权为 (0) 的边,然后从 (0) 号点向其他点跑最短路,在一开始就可以将所有点入队列,通过所有结点来更新,这样再用上面两种方式都可以判定出负环。

    具体看下图这个例子:在上面的原图上加了一个 (0) 点,可以手模一下,就会发现可以判出负环了。

    Code : AcWing 852

    // by pjx Aug.
    #include <iostream>
    #include <cstdlib>
    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    #include <cstring>
    #include <queue>
    #include <stack>
    #define REP(i, x, y) for(register int i = x; i < y; i++)
    #define rep(i, x, y) for(register int i = x; i <= y; i++)
    #define PER(i, x, y) for(register int i = x; i > y; i--)
    #define per(i, x, y) for(register int i = x; i >= y; i--)
    #define lc (k << 1)
    #define rc (k << 1 | 1)
    using namespace std;
    const int N = 1E4 + 5;
    int n, m;
    struct node{
        int v, w;
    };
    vector <node> g[N];
    queue <int> que;
    int cnt[N];
    int b[N], dis[N];
    int main()
    {
        cin >> n >> m;
        rep(i, 1, n)
        {
            g[0].push_back({i, 0});//建立“虚拟源点”
            que.push(i);
            b[i] = 1;
        }
        rep(i, 1, m)
        {
            int x, y, z;
            cin >> x >> y >> z;
            g[x].push_back({y, z});
        }
        b[0] = 1;
        while(!que.empty())
        {
            int k = que.front();
            que.pop();
            b[k] = 0;
            for(int j = 0; j < g[k].size(); j++)
            {
                int v = g[k][j].v;
                int w = g[k][j].w;
                if(dis[k] + w < dis[v])
                {
                    dis[v] = dis[k] + w;
                    cnt[v] = cnt[k] + 1;//这里用的是第二种判负环的方式
                    if(cnt[v] == n)//如果最短路走过的边数超过了n,则判定
                    {
                        cout << "Yes";
                        return 0;
                    }
                    if(!b[v])
                    {
                        b[v] = 1;
                        que.push(v);
                    }
                }
            }
        }
        cout << "No";
    	return 0;
    }
    
    
    
  • 相关阅读:
    iOS堆栈-内存-代码在据算机中的运行
    iOS self和super的区别
    php代码优化
    缓存雪崩现象解决方案
    缓存失效
    分布式memcache
    Linux下编译安装Memcache
    windows 下安装 php-memcached 扩展
    Linux下安装 php-memcache 扩展
    缓存之文件缓存
  • 原文地址:https://www.cnblogs.com/pjxpjx/p/15106515.html
Copyright © 2011-2022 走看看