SPFA的BFS与DFS实现及判负环问题

经过笔者的多次实践(失败),在此温馨提示:用SPFA判负环时一定要特别小心!

首先SPFA有BFS和DFS两种实现方式,两者的判负环方式也是不同的。

DFS是如果找到一个节点已经在递归栈中则表示出现负环(即你层层向下搜索后又回到了起点,很明显有负环),BFS是用一个数组记录每一个节点的入队次数,如果某个节点入队次数超过n(节点数)次则表示出现负环(根据SPFA的原理,每一个点最多被扩展n次就会出结果的,如果超过了n次算法还没结束,自然是有负环)。看起来是简单,但有以下注意事项:

  1. 如果只是判负环,使用DFS比BFS一般要快得多。
  2. 判断负环时,dis数组应该都设为0,这样可以使扩展节点时优先走负边,减少了很多冗余的运算,提高速度。当然这样dis数组也实际上失去了记录最短路的意义,因此鱼和熊掌,不可兼得!判负环与求最短路不能同时进行,如果既要判负环又要求最短路,须先用dis数组全部置0的SPFA判负环,如果没有负环再重新初始化dis等数组,然后再跑一遍正常的SPFA(下面就有一道例题,笔者被坑的不轻)。
  3. 用DFS判断负环,不能只把一个源点跑一次,而要把1-n每个都作为源点跑一遍SPFA,才能保证结果的正确。
  4. 还有一种比较玄学的判负环方式,就是正常地跑BFS的SPFA,如果扩展了MAXN个节点还没出结果,就判定有负环(MAXN为根据题目规模自拟的常量),原理简单易懂:跑了这么久还没出结果,当然是有负环咯~~NB的是经实测正确率还相当高!当然相当高还是牺牲了算法的正确性的,因此不到万不得已之时不建议使用(玄学你懂的)。

就只贴上DFS判负环的代码了(部分):

 1 void SPFA_DFS(int h)
 2  2 {
 3  3     if(flag) return ;
 4  4     register int p,v,w;//p为当前边,v为终点,w为边权
 5  5     vis[h]=true;
 6  6     for(p=tail[h];p;p=e[p].last)
 7  7     {
 8  8         v=e[p].v,w=e[p].w;
 9  9         if(dis[v]>dis[h]+w)
10 10         {
11 11             if(vis[v]){flag=true;return ;}//如果此节点已在递归栈中,则有负环
12 12             dis[v]=dis[h]+w;
13 13             SPFA_DFS(v);
14 14             if(flag) return ;
15 15         }
16 16     }
17 17     vis[h]=false;//这一步不要忘了,相当于回溯
18 18 }
19 19 int main()
20 20 {
21     memset(dis,0,sizeof dis);//全部置为0
22 21   for(register int i=1;i<=n;++i)
23 22   {
24 23 `    SPFA_DFS(i);//一定要把每个点都跑一次
25 24      if(flag) break;//flag代表有无负环
26 25   }
27 26   flag?printf("YES"):printf("NO");
28 27   return 0;
29 28 }

来一道很模板的例题:https://loj.ac/problem/10086

这怎么可以忍???从未见过如此猖狂的出题人!此题的特点是先判负环,如无负环求单源最短路。一开始我是小看这道题了,想用一次SPFA_DFS解决问题,结果有一个测试点负环没判到,还有一个点T了(说好的不必未超时担心呢=-=)。果然鱼和熊掌不可兼得,修改策略:先用SPFA_DFS判负环,如果没有再用正常的SPFA_BFS求最短路,成功AC,代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define INF 0x7ffffffffffffff
using namespace std;
const int MAXN=1000+5,MAXM=100000+5;
struct edge{
    int v,w,last;
}e[MAXM];
int n,m,s,tot,tail[MAXN];
long long dis[MAXN];
bool flag,vis[MAXN];
inline void add(int x,int y,int ww)
{
    tot++;
    e[tot]=(edge){y,ww,tail[x]};
    tail[x]=tot;
}
void DFS(int h)
{
    if(flag) return ;
    register int p,v,w;
    vis[h]=true;
    for(p=tail[h];p;p=e[p].last)
    {
        v=e[p].v,w=e[p].w;
        if(dis[v]>dis[h]+w)
        {
            if(vis[v]){flag=true;return ;}
            dis[v]=dis[h]+w;
            DFS(v);
            if(flag) return ;
        }
    }
    vis[h]=false;
}
void SPFA(int s)
{
    queue<int> q;
    register int h,p,v,w;
    vis[s]=true;
    q.push(s);
    do
    {
        h=q.front();q.pop();vis[h]=false;
        for(p=tail[h];p;p=e[p].last)
        {
            v=e[p].v,w=e[p].w;
            if(dis[v]>dis[h]+w)
            {
                dis[v]=dis[h]+w;
                if(!vis[v])
                {
                    q.push(v);vis[v]=true;
                }
            }
        }
    }while(!q.empty());
}
int main()
{
    register int i,x,y,z;
    scanf("%d%d%d",&n,&m,&s);
    for(i=1;i<=m;++i)
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
    }
    for(i=1;i<=n;++i) DFS(i);
    if(flag){printf("-1");return 0;}

    fill(dis+1,dis+n+1,INF);dis[s]=0;
    memset(vis,0,sizeof vis);
    SPFA(s);
    for(i=1;i<=n;++i){
        if(dis[i]==INF)
            printf("NoPath\n");
        else printf("%lld\n",dis[i]);
    }
    return 0;
}

要注意vis数组对于DFS和BFS的SPFA意义是不太一样的,判断负环与求最短路时对dis数组的初始化赋值也不一样。

来自某蒟蒻的强烈建议:用DFS判负环,用BFS求最小路!

希望能帮到大家,如有错误,敬请指正.

2018-08-18

原文地址:https://www.cnblogs.com/gosick/p/9497976.html

时间: 2024-07-31 00:04:51

SPFA的BFS与DFS实现及判负环问题的相关文章

【dfs判负环】BZOJ1489: [HNOI2009]最小圈

Description 找出一个平均边权最小的圈. Solution 经典问题,二分答案判断有无负环. 但数据范围大,普通spfa会超时,于是用dfs判负环(快多了). 思路是dis设为0,枚举每个点u,如果d(u)+w<d(v)就搜v,如果搜到的节点曾搜到过说明找到了负环. 感慨一下dfs真是神奇. Code 1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 using namespac

bzoj1690:[Usaco2007 Dec]奶牛的旅行(分数规划+spfa判负环)

前段时间准备省选没更,后段(?)时间省选考砸没心情更,最近终于开始恢复刷题了... 题目大意:有n个点m条有向边的图,边上有花费,点上有收益,点可以多次经过,但是收益不叠加,边也可以多次经过,但是费用叠加.求一个环使得收益和/花费和最大,输出这个比值. 显然这就是经典的分数规划题啊,就是最优比率环,那么就二分答案,将所有边(u,v)的边权改为[v的点权-(u,v)原边权*mid],这可以算是最优比率环的公式了吧,然后判一下是否有正环,有的话就说明答案可行.判正环有够别扭的,那就全部改成相反数然后

Poj3259--Wormholes(Spfa 判负环)

Wormholes Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 36836   Accepted: 13495 Description While exploring his many farms, Farmer John has discovered a number of amazing wormholes. A wormhole is very peculiar because it is a one-way p

LightOj 1221 - Travel Company(spfa判负环)

1221 - Travel Company PDF (English) Statistics Forum Time Limit: 2 second(s) Memory Limit: 32 MB A travel company is planning to launch their bus service in a new route. So they conducted a survey and made a list of all possible roads connecting diff

poj3259 Wormholes --- spfa判负环

又写了个bellman模板一直RE求解啊... #include <iostream> #include <cstring> #include <string> #include <cstdio> #include <cmath> #include <algorithm> #include <vector> #include <queue> #include <map> #define inf 0x

uva11090 Going in Cycle!! --- 二分+spfa判负环

给一个带权有向图,求其中是否存在环,若存在,输出环上边权的平均值最小的那个的平均值. 点的范围就50,感觉可以很暴力..但显然超时了 感觉方法好巧妙,二分平均值,将所有边权减去二分的那个值,然后spfa判断是否有负环 若有负环,则图中存在的所有环的边权平均值一定比枚举值大 反之则小,要是无论枚举值多大都没有负环,说明图中没有环. #include <iostream> #include <cstring> #include <string> #include <c

POJ-1860 Currency Exchange 【spfa判负环】

Description Several currency exchange points are working in our city. Let us suppose that each point specializes in two particular currencies and performs exchange operations only with these currencies. There can be several points specializing in the

BZOJ 3436 小K的农场 查分约束系统 SPFA判负环

题目大意:农场中有一些土地,上面会长一些作物,现在给出一些约束条件,问有没有这种可能. 思路:裸的查分约束系统判负环.记住要跑最长路. CODE: #include <queue> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define MAX 100010 using namespace std; int points,asks;

POJ 3259 Wormholes(SPFA判负环)

题目链接:http://poj.org/problem?id=3259 题目大意是给你n个点,m条双向边,w条负权单向边.问你是否有负环(虫洞). 这个就是spfa判负环的模版题,中间的cnt数组就是记录这个点松弛进队的次数,次数超过点的个数的话,就说明存在负环使其不断松弛. 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <queue> 5 using na