SPFA(Shortest Path Faster Algorithm)是Bellman-Ford算法的一种队列实现,减少了不必要的冗余计算。
算法大致流程是用一个队列来进行维护。 初始时将源加入队列。 每次从队列中取出一个元素,并对所有与他相邻的点进行松弛,若某个相邻的点松弛成功,则将其入队。 直到队列为空时算法结束。
这个算法,简单的说就是队列优化的bellman-ford,利用了每个点不会更新次数太多的特点发明的此算法
SPFA——Shortest Path Faster Algorithm,它可以在O(kE)的时间复杂度内求出源点到其他所有点的最短路径,可以处理负边。SPFA的实现甚至比Dijkstra或者Bellman_Ford还要简单
SPFA可以处理负权边
定理: 只要最短路径存在,上述SPFA算法必定能求出最小值。
证明:
每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点v的最短路径估计值d[v]变小。所以算法的执行会使d越来越小。由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。因此,算法不会无限执行下去,随着d值的逐渐变小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应结点的最短路径值。
期望的时间复杂度O(ke), 其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。
判断有无负环:
如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)
/*********************************************/ // d数组类似迪杰斯特拉的dis数组,记录起点到i点的局部最优解 // c数组用来记录访问 i 点的次数 // vis 记录是否在队列里面 // 用数组模拟邻接表存图,w数组为权值 /*********************************************/ bool spfa_bfs(int s) // s为图的起点 { queue <int> q; // 队列里存点 memset(d,0x3f,sizeof(d)); memset(c,0,sizeof(c)); memset(vis,0,sizeof(vis)); q.push(s); vis[s]=1; c[s]=1; d[s]=0; //顶点入队vis要做标记,另外要统计顶点的入队次数 while(!q.empty()) { int x; x=q.front(); q.pop(); vis[x]=0; //队头元素出队,并且消除标记 for(int k=f[x]; k!=0; k=nnext[k]) //遍历顶点x的邻接表 { int y=v[k]; if( d[x]+w[k] < d[y]) //如果可以松弛 { d[y]=d[x]+w[k]; //松弛 if(!vis[y]) //顶点y不在队内 不要重复入队列 { vis[y]=1; //标记 c[y]++; //统计次数 q.push(y); //入队 if(c[y]>NN) //超过入队次数上限,说明有负环 return false; } } } } return true; }
给出一道题目,练练手.
本人AC代码: 仅供参考 ,不对的地方请指出
大致思路:
用数组模拟邻接表存图,然后直接套模板.
#include <cstdio> #include <cstring> #include <cctype> #include <cmath> #include <set> #include <map> #include <list> #include <queue> #include <deque> #include <stack> #include <string> #include <bitset> #include <vector> #include <iostream> #include <algorithm> #include <stdlib.h> using namespace std; typedef long long LL; const int INF=2e9+1e8; const int MOD=1e9+7; const int MAX_SIZE=1005; int first[MAX_SIZE*2],nnext[MAX_SIZE*2]; int edge[MAX_SIZE*2][3],dist[MAX_SIZE],cnt[MAX_SIZE],vis[MAX_SIZE]; int V,E; bool spfa_bfs(int start) { memset(vis,0,sizeof(vis)); memset(cnt,0,sizeof(cnt)); memset(dist,0xf3,sizeof(dist)); queue<int>q; q.push(start); vis[start]=1; dist[start]=0; while(!q.empty()) { start=q.front(); q.pop(); vis[start]=0; int k=first[start]; while(k!=-1) { int terminal=edge[k][1]; if(dist[terminal]<dist[start]+edge[k][2]) { dist[terminal]=dist[start]+edge[k][2]; if(!vis[terminal]) { q.push(terminal); cnt[terminal]++; vis[terminal]=1; if(cnt[terminal]>V) return false; } } k=nnext[k]; } } return true; } int main() { // printf("sb "); int ncase; scanf("%d",&ncase); while(ncase--) { int i,j; scanf("%d %d",&V,&E); for(i=0; i<=1000; i++) first[i]=-1; for(i=0; i<E; i++) { int a,b,c,d,e,val1,val2; scanf("%d %d %d %d %d",&a,&b,&c,&d,&e); val1=d-c; val2=e-c; int xiaobiao=(i+1)*2-2; edge[xiaobiao][0]=a,edge[xiaobiao][1]=b,edge[xiaobiao][2]=val1; nnext[xiaobiao]=first[a]; first[a]=xiaobiao; xiaobiao++; edge[xiaobiao][0]=b,edge[xiaobiao][1]=a,edge[xiaobiao][2]=val2; nnext[xiaobiao]=first[b]; first[b]=xiaobiao; } int ans=spfa_bfs(0); // for(i=0;i<V;i++) // printf("%d ",dist[i]); // printf(" "); if(!ans) printf("$$$ "); else printf("%d ",dist[V-1]); } return 0; }