在Bellman-Ford中我们发现每一次都对所有的点进行松弛操作太浪费时间了,所以就有了spfa的出现
算法原理: 只有上一次循环中松弛过的点才有可能参与下一次循环的松弛操作,所以我们利用一个队列即可实现spfa算法
算法实现过程: 把起始点入队,然后取出队首元素,松弛相邻的点,如果松弛成功且这个点没有在队列里,那么就把这个点入队。直到队列为空。若有个点被松弛了n次,那么说明该图有负环(根据不同的需求spfa也可以判正环,判正环时找最长路即可)。
#include <iostream>
#include <vector>
#include <queue>
#define maxn 10
#define INF 0x3fffffff
using namespace std;
int dist[maxn],exist[maxn],cnt[maxn];
//cnt[i]表示从起点到i的最短距离包含的边数
struct node{
int val;
int num;
node( int a = 0,int b = 0 )
{
num = a;
val = b;
}
};
vector<node> g[maxn];
bool spfa(int begin,int n) //起始点和点的数量
{
queue<int> q;
for (int i = 1; i <= n; i++) //初始化距离
{
exist[i] = 0;
cnt[i] = 0;
dist[i] = INF;
}
dist[begin] = 0; //起点距离为0
q.push(begin);
exist[begin] = 1; //exist表示在队列里
while( !q.empty() )
{
int x = q.front();
q.pop();
exist[x] = 0; //松弛x
for (int i = 0; i < g[x].size(); i++)
{
node t = g[x][i];
if( dist[x] + t.val < dist[t.num] )
{
dist[t.num] = dist[x] + t.val;
cnt[t.num] = cnt[x] + 1;
if( !exist[t.num] ) //被更新且不在队列,即可入队
{
q.push(t.num);
exist[t.num] = 1;
}
if( cnt[t.num] >= n ) return true;
//如果从起点到t.num有大于等于n条边,那么说明一定有负环
}
}
}
return false;
}
int main()
{
int n,m;
cin >> n >> m;
for (int i = 0; i < m; i++)
{
int x,y,v;
cin >> x >> y >> v;
g[x].push_back(node(y,v));
}
if( spfa(1,n) ) cout << "no exsit shoest path" << endl; //存在负环
else
{
for (int i = 1; i <= n; i++)
{
cout << dist[i] << endl;
}
}
return 0;
}
关于复杂度,视每个图的情况而定,一般认为是O(KE),K一般不超过2。但是最坏情况下会变成O(VE),所以复杂度不稳定,当对于没有负边权的图计算最短路径建议还是使用Dijstra算法
spfa的优化
但是有些图就是负权图,但是题目还是要卡spfa,所以就有了两种优化方法,用的时候两种一起用
SLF:原队列改为双端队列,对一个要加入队列的点u,如果dis[u] 小于队首元素v的dis[v],那么就就加入到队首元素,否则加入到队尾。
LLL:对每个要出对的元素u,比较dis[u]和队列中dis的平均值,如果dis[u]更大,那么将它弹出放到队尾,取队首元素在进行重复判断,直至存在dis[x]小于平均值。
代码以函数的方式给出:
bool spfa()
{
int sum = 0; //记录队列元素和
deque<int> q;
q.push_back(0);
exist[0] = 1;
dist[0] = 0;
while( !q.empty() )
{
int s = q.size();
int x = q.front();
while( s*dist[x] > sum ) //出队时大于平均值,放到队尾去后面再出队(LLL)
{
q.pop_front();
q.push_back(x);
x = q.front();
}
exist[x] = 0;
q.pop_front();
sum -= dist[x];
for(int i = 0; i < g[x].size(); i++)
{
node t = g[x][i];
if( dist[t.num] > dist[x] + t.val )
{
dist[t.num] = dist[x] + t.val;
cnt[t.num] = cnt[x] + 1;
if( cnt[t.num] == n ) return true;
if( !exist[t.num] )
{
if( q.empty() || q.front() < dist[t.num] ) q.push_back(t.num); //如果要入队时队首元素更大,放队尾,否则放队首(SLF)
else q.push_front(t.num);
sum += dist[t.num];
exist[t.num] = 1;
}
}
}
}
return false;
}