最短路径——SPFA算法

时间:2023-02-09 23:22:33

关于SPFA(Shortest Path Faster Algorithm)算法,网上的实现与叙述已经有很多,所以在这里也不多说,在NOCOW上面有详细述说。

这个算法是对Bellman_Ford算法的一个队列优化,减少冗余计算。在计算带有负值边的图的最短路问题时是非常好的选择,当然在差分约束系统问题中也是首选。

这个算法的一个特点是,每个顶点可以进队不仅一次,在图中存在负权回路的时候,在该回路上的顶点入队次数会超过顶点总数,这也是判断图中是否存在负权回路的条件。

下面的算法是利用双端队列对一般的SPFA进行SLF优化的实现代码(SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)<dist(i),则将j插入队首,否则插入队尾。)

未优化的SPFA算法伪码如下

Procedure SPFA;
 
Begin
  initialize-single-source(G,s);
  initialize-queue(Q);
  enqueue(Q,s);
  while not empty(Q) do 
    begin
      u:=dequeue(Q);
      for each v∈adj[u] do 
        begin
          tmp:=d[v];
          relax(u,v);
          if (tmp<>d[v]) and (not v in Q) then
            enqueue(Q,v);
        end;
    end;
End;

下面是利用邻接表为存储结构,对SPFA进行SLF优化,并可以判断是否存在负权回路的C++实现

#include <iostream>
#include <deque>
#include <stack>
#include <vector>
using namespace std;

#define MAXN 100
#define INF 99999999

struct edge
{
	int to;
	int weight;
};

vector <edge> adjmap[MAXN]; //邻接表
bool in_queue[MAXN];  //顶点是否在队列中
int in_sum[MAXN];  //顶点入队次数
int dist[MAXN];  //源点到各点的最短路径
int path[MAXN];  //存储到达i的前一个顶点
int nodesum;  //顶点数
int edgesum;  //边数

bool SPFA(int source)
{
	deque <int> dq;
	int i,j,x,to;
	for(i=1;i<=nodesum;i++)
	{
		in_sum[i]=0;
		in_queue[i]=false;
		dist[i]=INF;
		path[i]=-1;
	}
	dq.push_back(source);
	in_sum[source]++;
	dist[source]=0;
	in_queue[source]=true;
	//初始化完成

	while(!dq.empty())
	{
		x=dq.front();
		dq.pop_front();
		in_queue[x]=false;
		for(i=0;i<adjmap[x].size();i++)
		{
			to=adjmap[x][i].to;
			if((dist[x] < INF)&&(dist[to] > dist[x]+adjmap[x][i].weight))
			{
				dist[to]=dist[x]+adjmap[x][i].weight;
				path[to]=x;
				if(!in_queue[to])
				{
					in_queue[to]=true;
					in_sum[to]++;
					if(in_sum[to] == nodesum)
						return false;
					if(!dq.empty())
					{
						if(dist[to] > dist[dq.front()])
							dq.push_back(to);
						else
							dq.push_front(to);
					}
					else
						dq.push_back(to);
				}
			}
		}
	}
	return true;
}

void Print_Path(int x)
{
	stack <int> s;
	int w=x;
	while(path[w] != -1)
	{
		s.push(w);
		w=path[w];
	}
	cout<<"顶点 1 到顶点 "<<x<<" 的最短路径长度为:"<<dist[x]<<endl;
	cout<<"所经过的路径为:1 ";
	while(!s.empty())
	{
		cout<<s.top()<<" ";
		s.pop();
	}
	cout<<endl;
}

int main()
{
	int i,s,e,w;
	edge temp;
	cout<<"输入顶点数和边数:";
	cin>>nodesum>>edgesum;
	for(i=1;i<=nodesum;i++)
		adjmap[i].clear();   //清空邻接表
	for(i=1;i<=edgesum;i++)
	{
		cout<<"输入第 "<<i<<" 条边的起点、终点还有对应的权值:";
		cin>>s>>e>>w;
		temp.to=e;
		temp.weight=w;
		adjmap[s].push_back(temp);
	}
	if(SPFA(1))
	{
		for(i=2;i<=nodesum;i++)
			Print_Path(i);
	}
	else
		cout<<"图中存在负权回路"<<endl;
	return 0;
}

下面是两个测试,第一个生成的图是和 Bellman_Ford算法中的测试数据一样的,这里就不再给出该图,可以通过连接去该文章中查看。

第二个测试是具有负权回路的图

最短路径——SPFA算法

最短路径——SPFA算法

下面是第二个测试的图,将其中顶点s编号为1,其余顶点的编号按照字母顺序编号。

最短路径——SPFA算法