SPFA算法简介

Bellman-Ford算法

Dijkstra算法要求图中不可有负权边,如果有负权边呢?对于存在负权边的图,我们采用Bellman-Ford算法来求解。
Bellman-Ford算法基于迭代思想。假设图有n个顶点,m条边,源点为1,基本步骤如下:

  1. 初始化dis数组
    • dis[1]初始化为0
    • 其他初始化为 + ∞ +\infty +
  2. 反复对m条边进行松弛操作,总共进行n-1次。
  3. 判断是否还会进行松弛操作(dis数组是否还会变化),如果仍然可以进行松弛,表示存在负权回路,无最短路径。否则dis数组中的值即为从源点出发的最短路径。

源点S确定后,dis[S]的距离确定,经过一轮边松弛操作,至少可以确定一个点的最短路径。反复进行迭代松弛操作,进行n-1次边松弛操作后,可以确定除去源点后全部n-1个顶点的最短路径。如果第n次松弛操作仍然可以进行,则表示存在负权回路,无最短路径。

代码

for(int i=1;i<n;i++)//最多n-1次
{
    for(int j=1;j<=m;j++)//m条边
        dis[e[j].to]=min(dis[e[j].to],dis[e[j]].from+e[j].w);
}

SPFA

SPFA即队列优化的bellman-ford算法,国内称为SPFA算法。
bellman-ford算法进行边松弛操作时,上一轮松弛中没有发生变化的顶点出发的边也会进行松弛,存在大量的冗余操作。SPFA算法对此情况进行了优化,只对发生变化的顶点出发的边进行松弛,如果所有顶点的距离都不再改变,算法结束。
基本操作:

  1. 初始化dis[1]为0,其他为+ ∞ \infty .
  2. 源点1入队列
  3. 从队列头中取出顶点,对从该点出发的边进行松弛操作,如果顶底的dis估值发生变化且不在队列中,将该点加入队列。
  4. 反复循环,直到队列为空为止。

代码

//SPFA
while(!q.empty())
{
	int t=q.front();q.pop();book[t]=0;
	for (int j=head[t];j!=-1;j=edge[j].next)
	{
		if (dis[edge[j].to]>dis[edge[j].from]+edge[j].w)
		{
			dis[edge[j].to]=dis[edge[j].from]+edge[j].w;
			if (!book[edge[j].to]) {q.push(edge[j].to);book[edge[j].to]=1;}
		}
	}
}

如果元素已经在队列中,只更新路径,无需再次入队。 一个点可能出队、入队多次,但不会同时在队列里出现多次

检测负权环

SPFA中,可以通过增加计数数组,统计每一个点被松弛的次数,判断是否存在负权环。

if (dis[edge[j].to]>dis[edge[j].from]+edge[j].w)
{
	dis[edge[j].to]=dis[edge[j].from]+edge[j].w;
	cnt[edge[j].to]++;
	if(cnt[edge[j].to]>=n)
	{
	    cout<<"负权边";//该边的更新出现n次,表示出现负环,且该边的w为负值。
	    return;
	}
	if (!book[edge[j].to]) {q.push(edge[j].to);book[edge[j].to]=1;}
}

输出路径

while(!q.empty())
{
	int t=q.front();q.pop();book[t]=0;
	for (int j=head[t];j!=-1;j=edge[j].next)
	{
		int v=edge[j].to, w=edge[j].w;
		if (dis[v]>dis[t]+w)
		{
			pre[v]=t; 
			dis[v]=dis[t]+w;
			if (!book[v]) {q.push(v);book[v]=1;}
		}
	}
}

使用pre数组记录节点的前向节点即可输出路径

你可能感兴趣的:(图论)