SPFA算法用于求单源最短路径,是Bellman-Ford算法的队列优化。尽管有人指出该算法O(E)的复杂度的理论证明是错误的,且在特别针对的图上,SPFA算法非常慢。而Dijkstra算法的理论证明严谨,表现也十分稳定。但是Dijkstra算法不能用于负权值的图,而Bellman-Ford算法效率又十分的低。所以仍然有很多人倾向于使用SPFA算法(SPFA实现比较简单,有人认为比Dijkstra和BF更简单)。实践表明,SPFA算法是完全可行的。一来理论证明错误,并不代表效率不能接受;二来也没有见过专卡SPFA的题目数据。近些年越来越多的题目开始卡SPFA,不过能解决负权值与负环,仍然是一个优点。所以,优先使用Dijkastra,然后再考虑SPFA。
SPFA的算法流程简单描述如下:
D数组保存源s到各点的距离,初始D[s] = 0,而其他个点设置为极大值例如INT_MAX
F数组用于保存标志位,表示对应点是否在队列中,初始全为false
C数组用于保存各点的入队次数,初始全为0。若某点的入队次数达到n次(n为顶点总数),则存在负环
建立队列,将s入队
当队列不为空时{
取出头结点u
对u的每一条出边对应的点v{
做松弛操作
如果v不在队列,则入队并维护F和C
若入队次数达到n次,有负环,结束算法
}
}
松弛操作就是维护D数组,算法结束后D数组就是结果,如果存在D[u]==INT_MAX,说明s和u是不连通的。
如果确定无负环,则可不必维护C数组。
下面是hdu1874的源代码,典型的单源最短路径,无负权值。
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
typedef int weight_t;
#define SIZE 2005
int N,M;
int S,T;
//边的结构
struct edge_t{
int node;
weight_t w;
edge_t* next;
}Edge[SIZE];
int ECnt = 0;
//图的邻接表
edge_t* Ver[202];
//初始化
inline void init(){
ECnt = 0;
fill(Ver,Ver+N,(edge_t*)0);
}
//生成双向边
inline void mkEdge(int a,int b,weight_t w){
Edge[ECnt].node = b;
Edge[ECnt].w = w;
Edge[ECnt].next = Ver[a];
Ver[a] = Edge + ECnt ++;
Edge[ECnt].node = a;
Edge[ECnt].w = w;
Edge[ECnt].next = Ver[b];
Ver[b] = Edge + ECnt ++;
}
//最短路径的结果
weight_t D[202];
//标志位
bool Flag[202];
//spfa算法,输入保证没有负环,s为源
void spfa(int s){
//初始化
fill(D,D+N,INT_MAX);
fill(Flag,Flag+N,false);
//初始化源
D[s] = 0;
Flag[s] = true;
//队列
queue<int> q;
q.push(s);
while( !q.empty() ){
int u = q.front();
q.pop();
Flag[u] = false;
//对u的每一条边
for(edge_t*p=Ver[u];p;p=p->next){
int v = p->node;
weight_t tmp = D[u] + p->w;
if ( D[v] > tmp ){
D[v] = tmp;
if ( !Flag[v] )
Flag[v] = true, q.push(v);
}
}
}
return;
}
bool read(){
if ( EOF == scanf("%d%d",&N,&M) )
return false;
init();
for(int i=0;i<M;++i){
int a,b,w;
scanf("%d%d%d",&a,&b,&w);
mkEdge(a,b,w);
}
scanf("%d%d",&S,&T);
return true;
}
int main(){
while( read() ){
spfa(S);
if ( INT_MAX == D[T] ) printf("-1\n");
else printf("%d\n",D[T]);
}
return 0;
}