Bellman-Ford算法的改进---SPFA算法
传送门:
SPFA
1.算法思想
Bellman-Ford算法时间复杂度比较高,在于Bellman-Ford需要递推n次,每次递推需要扫描所有的边,在递推n次的过程中,很多判断是多余的,所以考虑用队列优化,减少不必要的判断,这种算法称为SPFA(Shortest Path Faster Algorithm)
SPFA算法的大致流程就是用一个队列来进行维护,初始时将源点加入队列,每次从队列中取出一个顶点,并对它所有相邻的节点进行松弛,如果某个顶点松弛成功,则将其入队,重复这样的过程,直至队列为空为止。时间复杂度在O(Km)(通常K为2左右)一个顶点可以多次入队,但是如果有顶点入队次数大于n次,那就存在负环,此时应当返回存在负环信息
2.算法过程
在SPFA算法中同样可以用dist数组表示最短路长度,path数组保存路径,还需要设置cnt数组记录入队次数,vis数组记录当前是否在队列中
(1).取出队列头结点u,扫描从顶点u出发的每条边,设每条边的终点为v,边的权值为w(u, v)。如果dist[u] + w < dist[v],则将dist[v]修改成dist[u] + w<u, v>。修改path[v] = u,如果顶点v不在队列中,还需要将v加入队列并且入队次数加一。如果上述条件不成立就不做任何处理
(2).重复1直至队列为空或者某个顶点入队次数大于n
3.算法实现
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #include<queue> 7 #include<stack> 8 #include<map> 9 #include<set> 10 #include<sstream> 11 using namespace std; 12 typedef long long ll; 13 const int maxn = 1000 + 10; 14 const int INF = 1 << 25; 15 int T, n, m, cases; 16 struct Edge 17 { 18 int u, v, w; 19 Edge(){} 20 Edge(int u, int v, int w):u(u), v(v), w(w){} 21 }; 22 vector<Edge>edges;//把每一条边存下来 23 vector<int>Map[maxn];//G[i]这个vector存的是以i为起点的所有边在edges里面的下标 24 void init(int n) 25 { 26 for(int i = 0; i <= n; i++)Map[i].clear(); 27 edges.clear(); 28 } 29 void addedge(int u, int v, int w) 30 { 31 edges.push_back(Edge(u, v, w));//注意无向图需要存两条边 32 m = edges.size(); 33 Map[u].push_back(m - 1); 34 } 35 void Find(int u)//遍历以u为起点的所有边 36 { 37 for(int i = 0; i < Map[u].size(); i++) 38 { 39 Edge&e = edges[Map[u][i]]; 40 //使用e就可以遍历以u为起点的所有的边 41 } 42 } 43 int cnt[maxn]; 44 bool vis[maxn]; 45 int d[maxn], path[maxn]; 46 bool SPFA(int u) 47 { 48 queue<int>q; 49 memset(vis, 0, sizeof(vis));//初始化 50 memset(cnt, 0, sizeof(cnt)); 51 memset(path, -1, sizeof(path)); 52 for(int i = 0; i < n; i++)d[i] = INF; 53 d[u] = 0; 54 vis[u] = 1;//标记进入队列 55 q.push(u); 56 while(!q.empty()) 57 { 58 int u = q.front(); 59 q.pop(); 60 vis[u] = 0;//清除进入队列标记 61 for(int i = 0; i < Map[u].size(); i++) 62 { 63 Edge& e = edges[Map[u][i]]; 64 if(d[u] < INF && d[e.v] > d[u] + e.w) 65 { 66 d[e.v] = d[u] + e.w; 67 path[e.v] = Map[u][i];//path存的是边的下标,这样可以通过边找出之前的点以及每条路的路径,如果用邻接矩阵存储的话这里可以直接存节点u 68 if(!vis[e.v]) 69 { 70 q.push(e.v); 71 vis[e.v] = 1; 72 if(++cnt[e.v] > n)return true;//进队次数大于n,说明存在负环 73 } 74 } 75 } 76 } 77 for(int i = 0; i < n; i++) 78 { 79 if(i == u)continue; 80 printf("从%d到%d距离是:%2d ", u, i, d[i]); 81 stack<int>q;//存的是边的编号 82 int x = i;//x就是路径上所有的点 83 while(path[x] != -1) 84 { 85 q.push(x); 86 x = edges[path[x]].u;//x变成这条边的起点 87 } 88 cout<<u; 89 while(!q.empty()) 90 { 91 cout<<"->"<<q.top(); 92 q.pop(); 93 } 94 cout<<endl; 95 } 96 return false; 97 } 98 int main() 99 { 100 int c; 101 cin >> n >> c; 102 int u, v, w; 103 for(int i = 0; i < c; i++) 104 { 105 cin >> u >> v >> w; 106 addedge(u, v, w); 107 } 108 if(SPFA(0))cout<<"存在负环"<<endl; 109 else cout<<"不存在负环"<<endl; 110 return 0; 111 }
越努力,越幸运