经过笔者的多次实践(失败),在此温馨提示:用SPFA判负环时一定要特别小心!
首先SPFA有BFS和DFS两种实现方式,两者的判负环方式也是不同的。
DFS是如果找到一个节点已经在递归栈中则表示出现负环(即你层层向下搜索后又回到了起点,很明显有负环),BFS是用一个数组记录每一个节点的入队次数,如果某个节点入队次数超过n(节点数)次则表示出现负环(根据SPFA的原理,每一个点最多被扩展n次就会出结果的,如果超过了n次算法还没结束,自然是有负环)。看起来是简单,但有以下注意事项:
- 如果只是判负环,使用DFS比BFS一般要快得多。
- 判断负环时,dis数组应该都设为0,这样可以使扩展节点时优先走负边,减少了很多冗余的运算,提高速度。当然这样dis数组也实际上失去了记录最短路的意义,因此鱼和熊掌,不可兼得!判负环与求最短路不能同时进行,如果既要判负环又要求最短路,须先用dis数组全部置0的SPFA判负环,如果没有负环再重新初始化dis等数组,然后再跑一遍正常的SPFA(下面就有一道例题,笔者被坑的不轻)。
- 用DFS判断负环,不能只把一个源点跑一次,而要把1-n每个都作为源点跑一遍SPFA,才能保证结果的正确。
- 还有一种比较玄学的判负环方式,就是正常地跑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-09-29 23:02:19