SPFA的BFS与DFS实现及判负环问题
Posted gosick
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SPFA的BFS与DFS实现及判负环问题相关的知识,希望对你有一定的参考价值。
经过笔者的多次实践(失败),在此温馨提示:用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
");
else printf("%lld
",dis[i]);
}
return 0;
}
要注意vis数组对于DFS和BFS的SPFA意义是不太一样的,判断负环与求最短路时对dis数组的初始化赋值也不一样。
来自某蒟蒻的强烈建议:用DFS判负环,用BFS求最小路!
希望能帮到大家,如有错误,敬请指正.
2018-08-18
以上是关于SPFA的BFS与DFS实现及判负环问题的主要内容,如果未能解决你的问题,请参考以下文章