求负环-基于SPFA算法

在这里插入图片描述
一般情况下,我们用第二种。
在第一种中,如果有一个点入队n次,代表这个点被更新了n次。
负环: 一个有向图/无向图中 环路的边权和<0
因为是一个环,所以可以循环无限次,那么这些环上的点的距离就会变成-∞

求负环:
基于spfa
spfa 每入队一次 就相当于更新一次 如果入队>=n次
在bellman_ford中 每更新一次 最短距离变小 但一个点的最短距离不可能变小n次
1 统计每个点入队的次数 如果某个点入队n次
说明存在负环
←o
↓ ↑
o→o→o…o→o 总共n个点,则对于点i到其他点最多n-1条边,入队n次说明包含n条让dist[i]变小的边
同时因为更新原则是加上第n条边后最短路权重变小
所以第n条边是负的 则该路径一定存在负环
2 统计当前每个点的最短路中所包含的边数,如果某个点的最短路所包含的边数>=n
说明存在负环

n条边 则一定有n+1个点 但我们总共就n个点 所以这条最短路上一定有环
同时因为更新原则是加上第n条边后最短路权重变小
所以第n条边是负的 则该路径一定存在负环

推荐第2种方法:
考虑:
当数据如 -1
o←o
-1↓ ↑-1
o→o
-1
如果用第一种方法 转完一圈之后每个点只入队一次,达到判定要求则需要转n圈
O(n^2)
如果用第二种方法 转完一圈之后就能达到判定要求
O(n)

还有一个问题:
负环不一定从起点走到

4
↓ ↑
1→…2→3
解决方案:
将所有点入队的同时把所有点的距离初始化为0
q.push(node) for all_node
dist[node] = 0 for all_node
why 所有点入队? (结合虚拟源点建新图理解)
1 虚拟源点0向所有点连一条长度是0的边构成一条新的图
同时以虚拟源点0作为新图的起点
2 原图中存在负环 == 新图中存在负环
而新图里所有的负环一定能从虚拟源点出发走到
3 那么我们对新图做spfa时就是把虚拟源点0加入queue
而0 pop出来后队列会把所有原图的节点加入queue
why dist[node]=0?
1 有负环 == 做完spfa后 存在点i dist[i] = -∞
2 赋的初值0也是有限值,做完spfa后都会变成-∞
3 w[node]都是有限值 则必然要更新无限次(更新次数>=n)

最后来个玄学操作
spfa O(m) ~ O(nm)
当spfa效率比较低的时候(一直结束不了的时候)
等价于 存在负环

可测量化:当所有点入队次数超过2n,我们就认为图中很大可能存在负环

作者:仅存老实人
链接:https://www.acwing.com/solution/content/20506/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

trick做法,老老实实地求有可能超时。
在这里插入图片描述

//这道题也有收藏题解,利用抽屉原理,判断一下最短路径上是否有超过n-1
//条边,cnt[i]存i号点到源点的最短路上,边的数量。
//这道题让判断整个图中有无负环、上一题是求1到n的最短路,这道题也并不是让求从1开始的负环
//所以题解中有个思想很好,就是假设一个虚拟原点,初始把所有的点都加到队列中

/*
    多加一个0号顶点,到其他顶点的距离都是零,求0到其他顶点的最短路,如果0到i号顶点的最短路中超过了n-1个节点
    那么整个图中必定存在负环。那么本题中就必定存在负环,所以说开始把所有顶点都加入到队列中的操作,等于上述设虚拟原点
    的操作,上述虚拟原点的新图中,0到任意一个点有负环,就等于原来的图中一定存在负环,可以画个图理解一下
    
*/
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>

using namespace std;

const int N=100010;

int n,m;
int h[N],w[N],ne[N],e[N],idx;
int dist[N], cnt[N];
bool st[N];

void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

bool spfa()
{
    //这里的把dist数组初始化为正无穷的操作也就不用了
    //因为我们最后注意的是cnt数组,而不是dist,dist刚开始是0的话也无所依
    //图中存在负环的话,cnt必然会>=n,相当于所有距离都减去了正无穷,但是并不
    //影响最后cnt的判断
    
    //更牛逼的解释来了!
    //  观察这个更新操作,if(dist[j] > dist[t] + w[i]) ) 
    //如果存在负环,则一定会更新无穷次。cnt数组肯定会>=n的 !所以不初始化dist也没事!!
    queue<int> q;
    
    for(int i=1;i<=n;i++)
    {
        st[i]=true;
        q.push(i);
    }
    
    while(q.size())
    {
        int t=q.front();
        q.pop();
        
        st[t]=false;
        
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dist[t]+w[i])
            {
                dist[j]=dist[t]+w[i];
                cnt[j]=cnt[t]+1;
                
                if(cnt[j]>=n) return true;
                if(!st[j])
                {
                    q.push(j);
                    st[j]=true;
                }
            }
        }
    }
    return false;
}

int main()
{
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    
    if(spfa()) puts("Yes");
    else puts("No");
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值