Bellman-Ford算法的改进---SPFA算法

时间:2021-05-16 09:45:14

传送门:

 Dijkstra

 Bellman-Ford

 SPFA

 Floyd

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 }