SPFA 算法是 Bellman-Ford算法 的队列优化算法的别称,通常用于求含负权边的单源最短路径,以及判负权环。
技巧:
判断一个点在不在最短路上,只需要看起点到它的最短路+终点到它的最短路之和是否等于起点到终点的最短路
反向建边求从i点到起点的最短路(可用找增广路)
该算法的最坏复杂度为 O(VE),可以被构造数据卡掉,而且无论什么优化都是可以卡掉的。详情请看这个链接
所以说,关于spfa—他死了
所以说,跑正权图最好还是用Dijkstra
此算法朴素时间复杂度 O( n^2),堆优化极限O((m+n)logn)
跑正权图最好不要(不要)使用spfa,用dij。但是可以优化一点可以减少被卡掉的可能性
以下我介绍一下几种优化
该优化使用双端队列duque。基本操作可以度娘,在此不予与介绍
对一个要加入队列的点u,如果dis[u] 小于队首元素v的dis[v],那么就就加入到队首元素,否则加入到队尾。
核心代码
if(q.empty()||dis[to]>dis[q.front()])
q.push_back(to);
else q.push_front(to);
还有更优化的
容错后的SLF
定义容错值 val,当满足 dis[now] >dis[q.front()]+val时从队尾插入,否则从队首插入。
mcfx优化
定义区间 [l,r] ,当入队节点的入队次数属于这个区间的时候,从队首插入,否则从队尾插入。
代码实现
2. LLL(Large Label Last)优化
该优化其实并不常用,但是还是说一下思想
对每个要出对的元素u,比较dis[u]和队列中dis的平均值,如果dis[u]更大,那么将它弹出放到队尾,取队首元素在进行重复判断,直至存在dis[x]小于平均值
你甚至可以和第一种优化联用,其实没学习的必要
使用小根堆实现,其思想其实和SLF很像,将权值小的点放在队首。以减少松弛操作的次数。
大佬都是手写堆,我蒟蒻不会啊。我就介绍一下优先队列的实现。很简单的将队列变为优先队列就好了。时间复杂度我不敢保证,所以不是很推荐。但是手写堆的还行吧。手写堆为什么不给用Dij用(雾)
核心代码
struct cs{
int x;
bool operator < (const cs &rhs) const {
return x > rhs.x;
}
}a;
priority_queue<cs>q;
或者
priority_queue<int,vector<int>,greater<int> >q;
这应该是该算法最有存在价值的地方。
但实际上Bellman-Ford在这上面表现的更出色
介绍两种判负环的方法
思想:在一个无负环的图中从起点到任意一个点最短距离经过的点最多只有 n 个
实现:
Frist
记录起点到当前节点的最短距离包含点的个数,定义一个cnt数组来记录,如果包含点的个数大于点的总数n,则存在负环
核心代码如下
if(dis[to]>dis[u]+e[i].val)
{
dis[to]=dis[u]+e[i].val;
cnt[to]=cnt[u]+1;
if(cnt[to]>n)
return false;
}
second
记录节点的入队次数,可定义一个in数组记录,如次数大于点的总数n, 则存在负环
核心代码
if(in[to]>=n)return false;
if(!vis[to])
{
q.push(to)
vis[to]=1;
in[to]++;
}
思想:刚刚松弛过的点来松弛其他的点,如果能够松弛当前点并且当前点还在栈中,那图中存在负环。
核心代码
if(dis[to]>dis[u]+e[i].val)
{
if(vis[j])
{
flag=false;
return;
}
}
比如网络流找增广路之类的。
这里就不予与介绍了。