一:引入:
Bellman_ford算法会遍历所有的边,但是有很多的边遍历了其实没有什么意义,我们只用遍历那些到源点距离变小的点所连接的边即可,只有当一个点的前驱结点更新了,该节点才会得到更新;因此考虑到这一点,我们将创建一个队列每一次加入距离被更新的结点。而SPFA便是其的优化。
二:应用:
SPFA可以求最短路(有负权)也可以判断负环,有一点很重要的是它和dijkstra长的几乎一样,但是却意义不同:
Dijkstra算法中的str数组保存的是当前确定了到源点距离最小的点,且一旦确定了最小那么就不可逆了;SPFA算法中的str数组仅仅只是表示的当前发生过更新的点,且spfa中的st数组可逆(可以在标记为true之后又标记为false)。顺带一提的是BFS中的st数组记录的是当前已经被遍历过的点。
Dijkstra算法里使用的是优先队列保存的是当前未确定最小距离的点,目的是快速的取出当前到源点距离最小的点;SPFA算法中使用的是队列,目的只是记录一下当前发生过更新的点。
下面是SPFA算法求最短路的算法:
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int h[N],e[N],ne[N],w[N],idx;
int d[N];
bool str[N];
int n,m;
void add(int a,int b,int c)
{
e[idx]=b;
ne[idx]=h[a];
w[idx]=c;
h[a]=idx++;
}
int spfa()
{
memset(d,0x3f,sizeof d);
d[1]=0;
queue<int>q;
q.push(1);
str[1]=true;
while(q.size())
{
int Fx=q.front();
str[Fx]=false;
q.pop();
for(int i=h[Fx];i!=-1;i=ne[i])
{
int Tx=e[i];
if(d[Tx]>d[Fx]+w[i])
{
d[Tx]=d[Fx]+w[i];
if(!str[Tx])
{
q.push(Tx);
str[Tx]=true;
}
}
}
}
return d[n];
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
int t=spfa();
if(t==0x3f3f3f3f)cout<<"impossible";
else cout<<d[n];
}
我再粘一个dijkstra的代码
#include<bits/stdc++.h>
using namespace std;
const int N= 510;
int g[N][N],d[N];
bool str[N];
int n,m;
int dijkstra()
{
memset(d,0x3f,sizeof d);
d[1]=0;
for(int i=0;i<n;i++)
{
int t=-1;
for(int j=1;j<=n;j++)
{
if(!str[j]&&(t==-1||d[t]>d[j]))
t=j;
if(t==n)break;
}
str[t]=true;
for(int i=1;i<=n;i++)
{
d[i]=min(d[i],d[t]+g[t][i]);
}
}
if(d[n]==0x3f3f3f3f)return -1;
return d[n];
}
int main()
{
cin>>n>>m;
memset(g,0x3f,sizeof g);
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
g[a][b]=min(g[a][b],c);
}
cout<<dijkstra()<<endl;
}
可以很明显的发现,dijkstra的代码在更新最短路时,str在标记后是不可逆的,这就导致了在路径中一旦出现负权,那么它得到的值可能不是最短路的值,这取决于负权的大小,而SPFA不一样,它可逆,这说明负权是有二次判定的权力的。
然后是判定负环的方法:
统计当前每个点的最短路中所包含的边数,如果某点的最短路所包含的边数大于等于n,则也说明存在环,因为负环走一圈最短路一定会更新,如此,它便会一直在这个负环上一直走,然后便会爆程序(如果没有在n次边时跳出)。
下面还是代码展示:
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int h[N],e[N],ne[N],w[N],idx;
int d[N],cnt[N];
bool str[N];
int n,m;
void add(int a,int b,int c)
{
e[idx]=b;
ne[idx]=h[a];
w[idx]=c;
h[a]=idx++;
}
int spfa()
{
queue<int>q;
for(int i=1;i<=n;i++)
{
q.push(i);
str[i]=true;//全标记为true是为了能找到负环,因为负环会一直转圈。
}
while(q.size())
{
int Fx=q.front();
str[Fx]=false;
q.pop();
for(int i=h[Fx];i!=-1;i=ne[i])
{
int Tx=e[i];
if(d[Tx]>d[Fx]+w[i])
{
d[Tx]=d[Fx]+w[i];
cnt[Tx]=cnt[Fx]+1;
if(cnt[Tx]>=n)return true;
if(!str[Tx])
{
q.push(Tx);
str[Tx]=true;
}
}
}
}
return false;
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
if(spfa())cout<<"Yes";
else cout<<"No";
}