最短路径算法之四——SPFA算法

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 #include<stdio.h>
 2 #include<limits.h>
 3 #include<iostream>
 4 #include<string>
 5 #include<queue>
 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

 

posted @ 2014-07-23 11:55  Enumz  阅读(296)  评论(0编辑  收藏  举报