浅谈SPFA(队列优化的Bellman-Ford算法)

浅谈SPFA及其优化

  1. SPFA的前提—–Bellman-Ford算法
  2. SPFA的核心思想
  3. SPFA详解
  4. SPFA的优化
  5. 图中负环的判断

一、Bellman-Ford算法
Bellman-Ford 算法是一种用来求单源最短路径的一种算法,可以用于负边权上,但是如果有负环的话就没办法了(有负环可能算的出来吗?)
优点:便于理解,代码简短;
缺点:时间复杂度较高(保证为VE(边数*点数)),容易TLE,要仔细注意题的数据范围

Bellman-Ford算法的核心操作就是松弛,如果dis [ i ] + length [ i ] [ j ] < dis [ j ] ,就用dis [ i ] + length [ i ] [ j ] 更新dis [ j ] ;

算法步骤,枚举每个点,用每条边对其进行松弛操作,使答案不断逼近最优解,运行V-1次结束
算法正确性证明请参考百度百科
核心代码

for(int i=1;i<=n-1;i++)
{

    for(int j=1;j<=m;j++)
    {
        if(dis[u[j]]+val[j]<dis[v[j]])
        {
            dis[v[j]]=dis[u[j]]+val[j];
        } 
    }
}

当然我们可以考虑一个小优化,当循环到某个点i时已经无法松弛的时候,直退出循环

优化后的代码

for(int i=1;i<=n-1;i++)
{
    int zgs=0;
    for(int j=1;j<=m;j++)
    {
        if(dis[u[j]]+val[j] < dis[v[j]])
        {
            dis[v[j]]=dis[u[j]]+val[j];
            zgs=1;//判断是否已经松弛完了 
        } 
    }
    if(zgs==0) break;//松弛完了就直接退出,小优化; 
}

SPFA的思想
其实在国外并不承认SPFA,只是将这个算法称作队列优化的Bellman-Ford算法,其实也是显而易见的,但是因为在中国,SPFA算法的发明者段凡丁是独自发现的,其实在国外早就有了类似的优化
SPFA的思想其实很简单,从起点开始向其他点进行松弛,并把松弛后的点加入队列中,这样对所有点进行松弛,SPFA复杂度在随机数据下的复杂度约为O(kE)(k是一个很小的常数)
但在最坏情况下,SPFA的复杂度任仍下降到O(VE),所以在正权图中更推荐效率更高的dijkstra算法(当然如果dijkstra算法超时的话也只能用SPFA了)但SPFA和Bellman-Ford算法一样可以在负边上运行。

SPFA详解+代码(丑的话不要介意)
SPFA大概的算法步骤已经有所介绍了,就是从起点开始按照深度对每个点进行松弛操作,将松弛后的点逐个加入队列里,当队列里所有点都已经操作后便结束算法;

核心代码如下

inline void spfa(int s)
{

    fill(dis+1,dis+n+1,2147483647);
    memset(vis,false,sizeof(vis));
    dis[s]=0,que[1]=s,vis[s]=true;
    int head=0,tail=1,u;
    while(head<tail)
    {
        head++;
        vis[que[head]]=false;
        for(int u=adj[que[head]];u;u=nxt[u])
        {
            if(dis[que[head]]+val[u]<dis[to[u]])
            {
                dis[to[u]]=dis[que[head]]+val[u];
                if(vis[to[u]]==false)
                {
                    que[++tail]=to[u];
                    vis[to[u]]=true;                
                }
            }
        }
     }
}

SPFA的小优化
SPFA主要有两种优化策略,SLF和LLL,介绍引自百度百科

SPFA算法有两个优化策略SLF和LLL——SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)< dist(i),则将j插入队首,否则插入队尾; LLL:Large Label Last 策略,设队首元素为i,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出队进行松弛操作。SLF 和 LLF 在随机数据上表现优秀,但是在正权图上最坏情况为 O(VE),在负权图上最坏情况为达到指数级复杂度。

按照个人经验,随机数据下两种优化的效率大概为25%~40%左右,
实现较为简单,就不想贴代码了;

图中负环的判断

Bellman-Ford的判别方法很简单,对所有点松弛完后,再枚举所有边,如果有一条边仍能被松弛,那么说明图中有负环

代码

inline void bell(){
    for(int i=1;i<=n-1;i++)//松弛操作
    {
        int zgs=0;
        for(int j=1;j<=m;j++)
        {
            if(dis[u[j]]+val[j]<dis[v[j]])
            {
                dis[v[j]]=dis[u[j]]+val[j];
            } 
        }
    }
    falg=1;//falg记录是否存在负环
    for(int i=1;i<=m;i++)//判断负环
    {
        if(dis[v[i]]>dis[u[i]]+val[i])
        {
            falg=0;
            break;
        }
    }
}

而对于SPFA来说,一般有两种判断负环的方法

1、记录每个点入队的次数,如果某个点超过了N次,则存在负环
2、记录每个路径经过点的数量,如果存在某条路径经过的点的数量超过了N次,那么也说明存在负环

相比较而言,个人更偏向与第二种方法,实际数据对比的话第二种方法要比第一种方法要快那么一些,所以更偏向第二种方法

原文地址:https://www.cnblogs.com/forever-/p/9736098.html