关于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,其余顶点的编号按照字母顺序编号。