SPFA算法详解

SPFA算法详解SPFA算法~~它死了~~

大家好,又见面了,我是你们的朋友全栈君。

前置知识:Bellman-Ford算法

前排提示:SPFA算法非常容易被卡出翔。所以如果不是图中有负权边,尽量使用Dijkstra!(Dijkstra算法不能能处理负权边,但SPFA能)

前排提示*2:一定要先学Bellman-Ford!

0.引子

在Bellman-Ford算法中,每条边都要松弛\(n-1\)轮,还不一定能松弛成功,这实在是太浪费了。能不能想办法改进呢?

非常幸运,SPFA算法能做到这点。(SPFA又名“Bellman-Ford的队列优化”,就是这个原因。)

1.基本思想

先说一个结论:只有一个点在上一轮被松弛成功时,这一轮从这个点连出的点才有可能被成功松弛。

为什么?显而易见

好吧其实我当初也花了不少时间理解这玩意

松弛的本质其实是通过一个点中转来缩短距离(如果你看了前置很容易理解)。所以,如果起点到一个点的距离因为某种原因变小了,从起点到这个距离变小的点连出的点的距离也有可能变小(因为可以通过变小的点中转)。(通读三遍再往下看)

所以,可以在下一轮只用这一轮松弛成功的点进行松弛,这就是SPFA的基本思想。

2.用队列实现

我们知道了在下一轮只用这一轮松弛成功的点进行松弛,就可以把这一轮松弛成功的点放进队列里,下一轮只用从队列里取出的点进行松弛。

为什么是队列而不是其他的玄学数据结构?因为队列具有“先进先出,后进后出”的特点,可以保证这一轮松弛的点不会在这一轮结束之前取出。

干说可能不太理解,所以还是举个栗子吧。

<span role="heading" aria-level="2">SPFA算法详解

这又是之前的有向图,但是这次我们要用SPFA跑。

最开始,我们要把\(1\)号点放进队列(为什么要这样?先往下看)。\(dis\)数组和队列是这个亚子的:

\(i\) \(dis[i]\)
\(1\) \(0\)
\(2\) \(\infty\)
\(3\) \(\infty\)
\(4\) \(\infty\)
queue 1

\(1\)号点进行松弛(就是\(1\)号到\(1\)号再到目标点):

\(i\) \(dis[i]\)
\(1\) \(0\)
\(2\) \(1\)
\(3\) \(5\)
\(4\) \(\infty\)
queue 2 3

\(2,3\)号点被松弛成功了,把它们加入到队列里。

\(1\)号点被用过了,把它扔掉。(工具点石锤)

\(2\)号点进行松弛:

\(i\) \(dis[i]\)
\(1\) \(0\)
\(2\) \(1\)
\(3\) \(5\)
\(4\) \(3\)
queue 3 4

\(4\)号点被松弛成功了,把它们加入到队列里。

\(2\)号点被用过了,把它扔掉。

\(3\)号点进行松弛:

\(i\) \(dis[i]\)
\(1\) \(0\)
\(2\) \(1\)
\(3\) \(5\)
\(4\) \(3\)
queue 4

没有点被松弛成功。

\(3\)号点被用过了,把它扔掉。

\(4\)号点进行松弛:

\(i\) \(dis[i]\)
\(1\) \(0\)
\(2\) \(1\)
\(3\) \(4\)
\(4\) \(3\)
queue 3

\(3\)号点被松弛成功了,把它们加入到队列里。

\(4\)号点被用过了,把它扔掉。

\(3\)号点进行松弛:

\(i\) \(dis[i]\)
\(1\) \(0\)
\(2\) \(1\)
\(3\) \(4\)
\(4\) \(3\)
queue

没有点被松弛成功。

\(3\)号点被用过了,把它扔掉。

现在队列为空(也就是能松弛的都松弛了),算法结束。

3.Code

SPFA的具体实现,推荐结合上面的栗子食用。

#include <bits/stdc++.h>
using namespace std;
#define MAXN 10005
#define INF 0x7fffffff
int n,m,s,dis[MAXN];
vector<pair<int,int> > g[MAXN];//用vector存图,但是据说链式前向星更快
void spfa(){
    queue<int> q;
    q.push(s);//把初始点加入队列
    fill(dis+1,dis+1+n,INF);//因为一开始所有点都到不了,所以初始化为INF
    dis[s]=0;//自己到自己肯定距离为0
    while(!q.empty()){
        int u=q.front();
        q.pop();//从队列里取出第一个元素
        for(int i=0;i<g[u].size();i++){
            int v=g[u][i].first,w=g[u][i].second;
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                q.push(v);
            }
            //如果能松弛成功,那么松弛,把松弛成功的目标点放入队列
        }
    }
}
int main(){
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=m;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        g[u].push_back(make_pair(v,w));
    }//输入,建图
    spfa();
    for(int i=1;i<=n;i++){
        printf("%d ",dis[i]);
    }//输出
    return 0;
}

这个代码能够ACP3371,但是我还是推荐你自己码一遍。

4.特点

  • 能够处理有负权边的图,但是隔壁Dijkstra不行。
  • 在有负环的情况下,不存在最短路,因为不停在负环上绕就能把最短路刷到任意低。但是SPFA能够判断图中是否存在负环,具体方法为统计每个点的入队次数,如果有一个点入队次数$ \ge n $,那么图上存在负环,也就不存在最短路了。
  • 什么?你不知道什么叫负环?下面的就是。

<span role="heading" aria-level="2">SPFA算法详解

就是一个环,边权和是负。一般用一个名为菜鸡算法的算法判断

——Ynoi

  • SPFA的时间复杂度是\(O(km)\)\(k\)是每一个节点的平均入队次数,经过实践,\(k\)一般为\(4\),所以SPFA通常情况下非常高效。但是SPFA非常容易被卡出翔,最坏情况下会变成\(O(nm)\) 所以如果能用隔壁Dijkstra尽量不要用SPFA。至于具体怎么卡,据说是这样的:

<span role="heading" aria-level="2">SPFA算法详解

(这种图据说叫菊花图,能欺骗SPFA多次让点进入队列,所以\(k\)会变得非常大(上限为\(n\))。)

5.你都看到这了就不点一个赞吗?

这个最重要了qwq

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/154921.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • StretchDIBits 函数

    StretchDIBits 函数不知道各位有咩有被这个函数搞得很蛋疼,反正我是了,网上的文章很多其实都是到处copy,看了很多然并卵。这个函数的功能和参数就不多说了,蛋疼主要是它画的图片是倒着的,主要问题是怎么把他搞回来。网上的文章说了当目标宽度和源宽度的符号不一样他会做关于x轴的镜像,高度的符号不一样会做Y轴的镜像。好的我的开始函数是这样的StretchDIBits(bufferHDC,0,0,m_nVideoW

    2022年6月16日
    28
  • prototype.js教程及prototype中文手册

    prototype.js教程及prototype中文手册http://topmanopensource.iteye.com/blog/382425收集了网上的prototype.js教程及prototype中文手册,方便大家使用prototype.js1.4中文教程doc格式http://www.dayanmei.com/upload/prototype1.4.docprototype.js1.4中文教程以及prototype1.5英文教程以及p…

    2022年7月23日
    12
  • idea 2021.11.3激活【最新永久激活】

    (idea 2021.11.3激活)JetBrains旗下有多款编译器工具(如:IntelliJ、WebStorm、PyCharm等)在各编程领域几乎都占据了垄断地位。建立在开源IntelliJ平台之上,过去15年以来,JetBrains一直在不断发展和完善这个平台。这个平台可以针对您的开发工作流进行微调并且能够提供…

    2022年3月28日
    116
  • 搞定Android开发环境部署——非常详细的Android开发环境搭建教程[通俗易懂]

    搞定Android开发环境部署——非常详细的Android开发环境搭建教程[通俗易懂]引言在windows安装Android的开发环境不简单也说不上算复杂,本文写给第一次想在自己Windows上建立Android开发环境投入Android浪潮的朋友们,为了确保大家能顺利完成开发环境的搭建,文章写的尽量详细,希望对准备进入Android开发的朋友有帮助。 Android开发环境搭建分为以下四步:第一步、安装JDK;第二步、安装Eclipse;第三步、下载并

    2022年7月23日
    12
  • 注册会计师_会计师报考需要什么条件

    注册会计师_会计师报考需要什么条件本人是华政的,学的是国际法,成绩一般,从来没有上课的习惯。会计是零基础,6个月通过了注册会计师的会计、财务成本管理、税法、经济法、公司战略与风险管理5门课,想在这里和大家分享一下经验,也希望大家指教~~资料下载:http://www.iliyu.com/ 里面几乎什么资料都下的到          http://www.zhukuai.com/ 这个论坛8错          ht

    2022年10月4日
    4
  • java分布式学习路线

    java分布式学习路线先理解为什么需要分布式,因为服务器处理的能力需要提升,这里有两个方面,第一是纵向也就是增加cpu的能力,或者加内存;另一个方向就是横向,就是分布式。将本来一台计算机的压力分给多太计算机,从而可以平均分布io,同时提升响应速度。建议先从分布式数据库看起,之后你可以用虚拟机,和本机进行测试分布式数据库。之后你可以使用java操作这种分布式数据库。从而依旧用虚拟机练习web项目…

    2022年6月6日
    98

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号