• 浅谈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次,那么也说明存在负环

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

  • 相关阅读:
    ARRAYLIST使用方法
    学习如何把数据库数据提取为XML(转)
    jquery常用技巧及常用方法列表
    邮件发送
    DataSet/XMl相互操作
    jquery Tab效果和动态加载
    Ajax 显示XML
    dropdownlist动态数据绑定
    sql 拼接
    javascriptxmlxslt操作
  • 原文地址:https://www.cnblogs.com/forever-/p/9736098.html
Copyright © 2020-2023  润新知