关于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算法中的测试数据一样的,这里就不再给出该图,可以通过连接去该文章中查看。
第二个测试是具有负权回路的图
下面是第二个测试的图,将其中顶点s编号为1,其余顶点的编号按照字母顺序编号。