ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

图论学习笔记——SPFA判断负环

2021-08-09 20:02:38  阅读:217  来源: 互联网

标签:图论 int 短路 负环 SPFA que include dis


算法描述

有一个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;
}


标签:图论,int,短路,负环,SPFA,que,include,dis
来源: https://www.cnblogs.com/pjxpjx/p/15106515.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有