SPFA算法
SPFA可以用队列处理Bellman-Ford算法的优化,它可以处理带负边权的图,同时还可以判断负环。
SPFA算法步骤
-
起点为s,进入队列,计算它到所有邻居的距离。把s出队,状态有更新(最短距离有变化)的邻居入队,没有更新的不入队。队列中都是状态有变化的结点,只有结点状态变化才会影响最短的计算。
-
继续弹出队头元素u,更新u的邻居节点的状态。
-
有一个问题,弹出u之后,在后面的计算中u的状态可能还会更新(从另外一条路径到达u距离更小)。这很简单,如果u的状态更新就把u入队就可以了。
-
继续以上操作直到队列为空,说明此时没有结点状态更新。
-
判断负环:可以记录从s到u的最短路径上经过几次点。如果大于n个说明有负环。
注意:步骤3可以决定可能有很多结点重新进入队列,也可能很少,说明SPFA算法是不稳定的。
代码
int d[N],cnt[N];
int e[N],ne[N],w[N],idx;//e[i]位置i的边所指向的结点 ne[i]位置i所指向的下一个位置 w[i]位置i的边的权值
int h[N];//h[u]表示与结点u连接的第一条边的位置
int b;//起点
bool vis[N];//表示结点i是否在队列中。
int n,m;
void init()
{
memset(d,0x3f,sizeof d);//初始化初始距离为无限大
memset(h,-1,sizeof h);//初始化h所指向的位置为-1
}
void add(int a,int b,int c)//往图中添加边和结点
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
int SPFA()
{
queue<int> q;
q.push(b);
d[b]=0;
vis[b]=1;
while(!q.empty())
{
int t=q.front();q.pop();//队列中的结点,然后删除
vis[t]=0;//设置结点t不在队列中
for(int i=h[t];~i;i=ne[i])//遍历以from为起点的边
{
int to=e[i];
if(d[to]>d[t]+w[i])
{
d[to]=d[t]+w[i];
cnt[to]=cnt[t]+1;//到达结点to经过t结点,需要+1;
if(cnt[to]>=n) return 1;//存在负环
if(!vis[to]) //如果结点to不在队列中,添加到队列中
{
q.push(to);
vis[to]=1;
}
}
}
}
return 0;//没有负环
}