SPFA最短路算法

302 阅读2分钟

SPFA算法

SPFA可以用队列处理Bellman-Ford算法的优化,它可以处理带负边权的图,同时还可以判断负环。

SPFA算法步骤

  1. 起点为s,进入队列,计算它到所有邻居的距离。把s出队,状态有更新(最短距离有变化)的邻居入队,没有更新的不入队。队列中都是状态有变化的结点,只有结点状态变化才会影响最短的计算。

  2. 继续弹出队头元素u,更新u的邻居节点的状态。

  3. 有一个问题,弹出u之后,在后面的计算中u的状态可能还会更新(从另外一条路径到达u距离更小)。这很简单,如果u的状态更新就把u入队就可以了。

  4. 继续以上操作直到队列为空,说明此时没有结点状态更新。

  5. 判断负环:可以记录从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;//没有负环 
}