最近刚刚学习了一下SPFA算法,发现其实也是不大难的,也许只是会者不难吧
下面我来谈一下作为博客园新人对于SPFA算法的理解,我会给出c++代码
首先 SPFA 算法是用来求最短路径的,实际上这个算法在国际中并不被认可,但是它的确是一个高效的最短路算法,它是Bellman-Ford算法的优化版本,时间复杂度为O(kE),k的值一般情况下比较小,所以比较适合来求最短路径。
接下来,讲一下SPFA算法的思想。
SPFA算法的思想大概是这样的(这里直接用链表来讲吧)
创建一个链表来存储题目给出边的信息,这是SPFA执行之前必须的工作,否则会发现,要么非常难写,要么写的不对,时间效率也下来了
在c++中有两种链表储存方式,一种是手写数组,另一种则是STL模版库里面的vector不定长数组,代码在讲解结束的时候再与SPFA核心代码一并给出
接下来是SPFA的核心思想:
首先建立一个d数组来记录每个节点i到起点s的最短路径长度,将d数组所有初始值全部赋为正无穷(其实在写程序的时候,自己定义一个比较大的数INF然后都赋值成INF就好了)
创建一个队列,由于本人习惯使用STL中的queue队列,所以,本人的代码中会有queue出现。queue的基本操作:q.push(value):将元素value入队,无返回值;q.front():取出队列首部元素,有返回值;q.pop():弹出队列首部元素,无返回值;
将d[s]赋值成为0,并以节点s扩展更新与s相邻节点到s的最短距离,并将更新成功的节点入队,弹出s,并且每次用队列的首元素更新与这个元素相邻的节点的d[i]值,并将成功更新的节点加入队列。(这里要注意,有可能被更新成功的节点已经在队列中,这时就无需再次将其加入队列,只需将被成功更新并且没有在队列中的节点入队即可)并且使用队列首元素更新完成后将取出的队列首元素弹出队列,这样循环一直到队列为空的时候停止。
在这里举个例子吧:比如在一个图中,有1、2、3、4、5这5个节点,现在以1为起始点,1加入队列q,并记录1已经在队列中,1更新了2、3、4,那么就将2、3、4加入队列,(此时d[2]、d[3]、d[4]已经更新),将1弹出队列,并且设置1不在队列中,接下来取2更新,检索到了3、4、5,成功更新了4、5,,4无需再进队列,5进队列,弹出2,设置2不在队列中,再用3去更新,此时更新到了2,那么2再次进入队列,这样不断循环,直到q为空为止;
下面给出代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <cstring> 5 #include <vector> 6 #include <queue> 7 #define maxn 1001 8 #define INF 100000000 9 using namespace std; 10 struct edge{ 11 int from,to,dist; 12 edge(int a,int b,int c):from(a),to(b),dist(c){}; 13 }; 14 vector<edge>edges; 15 vector<int>f[maxn]; 16 int n,m,j,k,l; 17 int d[maxn]; 18 bool v[maxn];//queue_visited[1001]; 19 int spfa(int s){//核心代码 20 memset(v,false,sizeof(v)); 21 for(int i = 1;i <= n;++i){ 22 d[i] = INF; 23 } 24 queue<int>q; 25 d[s] = 0; 26 v[s] = true; 27 q.push(s); 28 while(!q.empty()){ 29 int x = q.front(); 30 q.pop(); 31 v[x] = 0; 32 for(int i = 0;i < f[x].size();++i){ 33 edge o = edges[f[x][i]]; 34 if(o.dist + d[x] < d[o.to]){ 35 d[o.to] = o.dist + d[x]; 36 if(!v[o.to]){ 37 v[o.to] = 1; 38 q.push(o.to); 39 } 40 } 41 } 42 } 43 return d[n]; 44 } 45 int main(){ 46 scanf("%d%d", &n, &m); 47 for(int i = 1;i <= m;++i){//链表存储 48 cin >> j >> k >> l; 49 edges.push_back(edge(j,k,l)); 50 edges.push_back(edge(k,j,l)); 51 int count = edges.size(); 52 f[j].push_back(count-2); 53 f[k].push_back(count-1); 54 } 55 int ans = spfa(1); 56 cout<<ans<<endl; 57 return 0; 58 }
当题目明确可能有负环的时候,可以通过一个元素进入队列次数来判断,如果一个元素进入队列超过n次,则说明有负环
当然我用了一些STL的东西,不用STL的话,请读者自己思考一下