SPFA算法的判负环问题(BFS与DFS实现)
经过笔者的多次实践(失败),在此温馨提示:用SPFA判负环时一定要特别小心!
首先SPFA有BFS和DFS两种实现方式,两者的判负环方式也是不同的。
BFS是用一个num数组,num[x]表示从1到x的最短路径包含的边数,当执行松弛操作d[y]=d[x]+w时,同样更新num[y]=num[x]+1,若此时发现num[y]>=n,则图中有负环(显然,n个点n条不重的边,必定又环)。DFS则是换了一种思路:把d数组的初值置为0,这样就能保证走过的路径和一直为负,排除了大量无关路径。但是这样判断的是是否有经过起始点的负环,因此要判断整个图中是否有负环的话,得把n个点全跑一遍。看起来是简单,但有以下注意事项:
- 如果只是判负环,使用DFS比BFS一般要快得多。
- DFS判断负环时,dis数组初值应该都设为0。
- 不要指望DFS在判断负环的同时还能求最短路了。
- 用DFS判断负环,不能只把一个点作为源点跑一次,而要把1-n每个都作为源点跑一遍SPFA,才能保证结果的正确。
- 还有一种比较玄学的判负环方式,就是正常地跑BFS的SPFA,如果扩展了MAXN次还没出结果,就判定有负环(MAXN为根据题目规模自拟的常量),原理简单易懂:跑了这么久还没出结果,当然是有负环咯~~NB的是经实测正确率还相当高!当然相当高还是牺牲了算法的正确性的,因此不到万不得已之时不建议使用(玄学你懂的)。
BFS判负环(部分):
1 bool SPFA(){
2 queue<int> q;
3 for(int i=1;i<=n;++i) d[i]=INF;
4 memset(num,0,sizeof num);
5 while(!q.empty()){
6 int h=q.front();q.pop();
7 vis[h]=false;
8 for(int p=G.tail[h];p;p=G.e[p].last){
9 int &v=G.e[p].v,&w=G.e[p].w;
10 if(d[v]>d[h]+w){
11 d[v]=d[h]+w;
12 num[v]=num[h]+1;
13 if(num[v]>=n) return true;
14 if(!vis[v])
15 q.push(v),vis[v]=true;
16 }
17 }
18 }
19 return false;
20 }
BFS判负环的另一种方式是用num[x]记录x入队的次数,如过某个num[x]>=n则判定有负环。但这种方法一般不如上面介绍的快,例如在n个结点构成一个负环的图中(这也是一种常见的卡的图),上面的方法只需绕环一次即可判定负环,而这种方法则需绕环n次。
DFS判负环(部分):
1 bool SPFA(int x){
2 vis[x]=true;
3 for(int p=G.tail[x];p;p=G.e[p].last){
4 int &v=G.e[p].v,&w=G.e[p].w; 5 if(d[v]>d[x]+w){
6 d[v]=d[x]+w;
7 if(vis[v]){vis[x]=false;return true;}
8 if(SPFA(v)){vis[x]=false;return true;}
9 }
10 }
11 return vis[x]=false;
12 }
13 bool check(){
14 memset(d,0,sizeof d);
15 for(int i=1;i<=n;++i) if(SPFA(i)) return true;
16 return false;
17 }
为什么第7和第8行要写个vis[x]=false?因为我们没有执行第11行,如果不写的话就无法把vis数组置0了,这样我们每次SPFA都得memset(vis,0,sizeof vis),很麻烦。
来一道很模板的例题:https://loj.ac/problem/10086
如此猖狂的出题人怎么可以忍???此题的特点是先判负环,若无负环求单源最短路。一开始我是小看这道题了,想用一次SPFA_DFS解决问题,结果有一个测试点负环没判到,还有一个点T了(说好的不必为超时担心呢=-=)。果然鱼和熊掌不可兼得,修改策略:先用SPFA_DFS判负环,如果没有再用正常的SPFA_BFS求最短路,就可以A了。
要注意vis数组对于DFS和BFS的SPFA意义是不太一样的,判断负环与求最短路时对dis数组的初始化赋值也不一样。
本蒟蒻建议:用DFS判负环,用BFS求最小路。
2018-08-18
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· 趁着过年的时候手搓了一个低代码框架
· 推荐一个DeepSeek 大模型的免费 API 项目!兼容OpenAI接口!