SPAF算法
求单源最短路的SPFA算法的全称是:Shortest Path Faster Algorithm,该算法是西南交通大学段凡丁于1994年发表的。
它可以在O(kE)的时间复杂度内求出源点到其他所有点的最短路径。
其中k为所有顶点进队的平均次数,可以证明k一般小于等于2,可以处理负边,但无法处理带负环的图(负环和负边不是一个概念)。
SPFA的实现甚至比Dijkstra或者Bellman_Ford还要简单。
SPFA算法过程:
我们记源点为S,由源点到达点i的“当前最短路径”为D[i],开始时将所有D[i]初始化为无穷大,D[S]则初始化为0。算法所要做的,就是在运行过程中,不断尝试减小D[]数组的元素,最终将其中 每一个元素减小到实际的最短路径。
过程中,我们要维护一个队列,开始时将源点置于队首,然后反复进行这样的操作,直到队列为空:
(1)从队首取出一个结点u,扫描所有由u结点可以一步到达的结点,具体的扫描过程,随存储方式的不同而不同;
(2)一旦发现有这样一个结点,记为v,满足D[v] > D[u] + w(u, v),则将D[v]的值减小,减小到和D[u] + w(u, v)相等。其中,w(u, v)为图中的边u-v的长度,由于u-v必相邻,所以这个长度一定已知(不然我们得到的也不叫一个完整的图);这种操作叫做松弛。
(3)上一步中,我们认为我们“改进了”结点v的最短路径,结点v的当前路径长度D[v]相比于以前减小了一些,于是,与v相连的一些结点的路径长度可能会相应地减小。注意,是可能,而不是一定。但即使如此,我们仍然要将v加入到队列中等待处理,以保证这些结点的路径值在算法结束时被降至最优。
判断有无负环:
如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)
对于不存在负权回路的图来说,上述算法是一定会结束的。因为算法在反复优化各个最短路径长度,总有一个时刻会进入“无法再优化”的局面,此时一旦队列读空,算法就结束了。然而,如果图中存 在一条权值为负的回路,就糟糕了,算法会在其上反复运行(因为d[]加上一个负数肯定变下了,所以在有负环的情况下,会不断有数进入队列),通过“绕圈”来无休止地试图减小某些相关点的最短路 径值。假如我们不能保证图中没有负权回路,一种“结束条件”是必要的。这种结束条件是什么呢?
思考Bellman-Ford算法,它是如何结束的?显然,最朴素的Bellman-Ford算法不管循环过程中发生了什么,一概要循环|V|-1遍才肯结束。凭直觉我们可以感到,SPFA算法“更聪明一些”,就是说我 们可以猜测,假如在SPFA中,一个点进入队列——或者说一个点被处理——超过了|V|次,那么就可以断定图中存在负权回路了。
SPFA代码实现(以HDU1535为例):
1 #include2 #include 3 #include 4 #include<string> 5 #include 6 #define MAXN 1000000 7 using namespace std; 8 struct e 9 { 10 int begin; 11 int end; 12 int dis; 13 } edge1[MAXN+10],edge2[MAXN+10]; 14 int dis[MAXN+10],first[MAXN+10]; 15 bool vis[MAXN+10]; 16 int T,S,D,N,k,M; 17 void SPFA(int begin,struct e edge[]) 18 { 19 for (int i=1; i<=N; i++) 20 { 21 dis[i]=INT_MAX; 22 vis[i]=0; 23 } 24 queue <int> Q; 25 Q.push(begin); 26 dis[begin]=0; 27 while (!Q.empty()) 28 { 29 begin=Q.front(); 30 Q.pop(); 31 vis[begin]=0; 32 for (int i=first[begin]; edge[i].begin==begin; i++) 33 if (dis[edge[i].end]>dis[begin]+edge[i].dis) 34 { 35 dis[edge[i].end]=dis[begin]+edge[i].dis; 36 if (!vis[edge[i].end]) 37 { 38 Q.push(edge[i].end); 39 vis[edge[i].end]=1; 40 } 41 } 42 } 43 } 44 void init(struct e edge[]) 45 { 46 memset(first,0,sizeof(first)); 47 first[edge[1].begin]=1; 48 for (int i=2; i<=M; i++) 49 if (edge[i-1].begin!=edge[i].begin) first[edge[i].begin]=i; 50 } 51 bool cmp(struct e a,struct e b) 52 { 53 return a.begin<b.begin; 54 } 55 int main() 56 { 57 int T; 58 cin>>T; 59 while (T--) 60 { 61 scanf("%d %d",&N,&M); 62 int x1,x2,x3; 63 for (int i=1; i<=M; i++) 64 { 65 scanf("%d %d %d",&x1,&x2,&x3); 66 edge1[i].begin=x1,edge1[i].end=x2,edge1[i].dis=x3; 67 edge2[i].begin=x2,edge2[i].end=x1,edge2[i].dis=x3; 68 } 69 sort(edge1+1,edge1+M+1,cmp); 70 sort(edge2+1,edge2+M+1,cmp); 71 init(edge1); 72 SPFA(1,edge1); 73 int cnt=0; 74 for (int i=1; i<=N; i++) 75 cnt+=dis[i]; 76 init(edge2); 77 SPFA(1,edge2); 78 for (int i=1; i<=N; i++) 79 cnt+=dis[i]; 80 printf("%d\n",cnt); 81 } 82 return 0; 83 }
期望的时间复杂度:O(ke), 其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。