我可能要退役了……

退役之前,写一篇和我一样悲惨的算法:SPFA

最短路算法(二)SPFA算法

Part 1:SPFA算法是什么

其实呢,SPFA算法只是在天朝大陆OIers的称呼,它的正统名字叫做:队列优化的Bellman-Ford算法

在天朝,我们把它叫做“Shortest Path Fast Algorithm(SPFA)”翻译过来就是“快速最短路算法”

Part 2:SPFA算法的原理和实现思路

声明:以下的三元组(x,y,z)表示点 i ->点 j 有边权值为z,dis[x]表示出发点到编号为x节点距离

算法原理:

给定一张有向图,如果对于图中任意一条边(x,y,z)有 dis[y]<=dis[x]+z 成立,那么称这条边满足三角不等式

如果所有的边都满足三角不等式,则dis[x]就是出发点到x结点的最短路长度

正确性请读者自证(滑稽)

实现方法:

建立一个队列,最初队列里只有初始结点(假设为1)

取出队头节点x,扫描它的所有出边(x,y,z),如果不满足三角不等式 (dis[y]>dis[x]+z),更新 dis[y]=dis[x]+z,同时,如果此时 y 点不在队列中,把 y 点入队

重复上面的步骤,直到队列为空(此时所有边都满足三角不等式,得到单源最短路的解)

Part 3:SPFA算法性能、适用范围、初始化注意事项

上面说完了SPFA算法的思路框架,在看代码之前我们需要了解这些事项,来加深对这个算法的理解,避免在竞赛中使用错误的算法导致失分

1、适用范围:有向图、无向图、负权图,可以在出现负权回路时报错

该算法适用范围很广,值得一提的是:如果一个节点被入队了n(n是节点数)次,则图中存在负权回路

2、时间复杂度:

关于SPFA:

他死了

这个烂梗是怎么来的呢?其实这也是SPFA的死因——不稳定

很多编程的书上明确的写到:SPFA的时间复杂的为O(km),其中m是边数,k是小常数(约等于2)

很多童鞋就发现:“woc!这个算法不但可以处理负权图,负权回路可以报错,时间复杂度还小,真香啊!”

However,并不是这样的。。。有利必有弊,书中还有一句话很多人忽略掉了:

在特殊构造的图中,该算法复杂度很有可能退化

“退化”具体到什么程度呢?O(nm),其中n是节点数,m是边数,所以,对于某些特殊构造的完全图来说,用SPFA来实现最短路是很慢的(这个比n^2的复杂度还要高)

具体的能卡死SPFA的数据类似于这样:一开始诱导SPFA进入错误的最短路方向,先让他把整个图更新一遍,但是回头看,这个并不是最短路,于是再次花大量的时间进行重复更新,举个实例,链状+菊花状(网格图),这样构造的数据很容易就可以把SPFA卡死。

这里附上一位大佬的博客,里面有卡SPFA的思路,甚至还有hackSPFA数据生成器,有兴趣的朋友可以自己出一个题卡一卡SPFA,在这里我就不Copy了。https://blog.csdn.net/qq_45721135/article/details/102472101

不仅如此,更加丧心病狂的是:近几年出题人开始有意识的卡SPFA算法,导致很多SPFA算法的使用者失掉了分数,于是网上开始大传“SPFA已死”这样的评论

这里,我只能提醒大家一句我自己总结的规律:没有负权的最短路问题一定是在卡SPFA!

3、空间复杂度:

O(m),用邻接表没什么好说的

4、码长:

算法主体代码长度:约400B

整个求最短路代码长度:约950B

5、初始化注意事项:

dis[n],记录最短路的数组初始化为0x3f,vis[n],记录元素是否在队列里,初始化为0,另外,需要一个空的队列

Part 4:SPFA算法结构框架

这里给出我的程序,仅供参考,欢迎hack

#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<cstring>
#define N 101000
using namespace std;
typedef unsigned int unint;//闲的没事的typedef
struct edge{
int to,cost;//结构体构建邻接表
};
vector<edge>v[N];//邻接表
queue<int>q;//队列
int dist[N],vis[N],n,m;
void spfa(int s)//s是起点
{
memset(dist,0x3f,sizeof(dist));//初始化距离无限大
memset(vis,,sizeof(vis));//所有元素都不在队列里
dist[s]=;//初始化起点距离是0
vis[s]=;//起点在队列里
q.push(s);//起点入队
while(q.size()!=)//队列不为空,执行循环
{
int x=q.front();//取出队首
q.pop();
vis[x]=;//元素不在队列里
for(unint i=;i<v[x].size();i++)//避免Dev警告,强迫症unint
{
int y=v[x][i].to;//x点可以到的结点
int z=v[x][i].cost;//边的权值
if(dist[y]>dist[x]+z)//不满足三角不等式,进行更新
{
dist[y]=dist[x]+z;
if(vis[y]==)//如果y不在队列里,入队
{
q.push(y);
vis[y]=;
}
}
}
}
}

Part 5:用SPFA切题

https://www.luogu.com.cn/problem/P1339

由于我并不是SPFA算法的爱好者,所以我做题的时候能不用SPFA就不用SPFA,我翻了翻我之前的代码,发现SPFA竟然只有一份,那就放上来吧

显然这是一个最短路的板子题

思路也很简单,先初始化好邻接表和起始点,终点,然后跑一边SPFA,输出dis[end]即可

下面上代码:

#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<cstring>
#define N 101000
using namespace std;
typedef unsigned int unint;
struct edge{
int to,cost;
};
vector<edge>v[N];
queue<int>q;
int dist[N],vis[N],n,m,st,end;
void spfa(int s)
{
memset(dist,0x3f,sizeof(dist));
memset(vis,,sizeof(vis));
dist[s]=;
vis[s]=;
q.push(s);
while(q.size()!=)
{
int x=q.front();
q.pop();
vis[x]=;
for(unint i=;i<v[x].size();i++)
{
int y=v[x][i].to;
int z=v[x][i].cost;
if(dist[y]>dist[x]+z)
{
dist[y]=dist[x]+z;
if(vis[y]==)
{
q.push(y);
vis[y]=;
}
}
}
}
}
int main()
{
scanf("%d%d%d%d",&n,&m,&st,&end);//输入起始点和终点
for(int i=;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);//初始化邻接表
v[x].push_back((edge){y,z});
v[y].push_back((edge){x,z});
}
spfa(st);//以st为起点跑一边spfa
printf("%d",dist[end]);//输出dist[end]的值(st->end的最短路径)
return ;
}

Part 6:卡SPFA实况

为了防止一些同学不听我的忠告,下面对SPFA算法进行公开处刑(展示SPFA华丽丽的TLE)

https://www.luogu.com.cn/problem/P4779

当时我一看这个题,woc这不就是一个板子题吗,直接贴了一个SPFA上去

正当我得意洋洋的看着正在评测,想着这题必A的时候,4个TLE让我傻眼了

看了题解才知道要用Dijkstra+二叉堆优化才能过这个题。

本来还想写Dij算法的,但是我这波退役的匆忙,没有时间再写了,这里就把题解里的AC代码放上,大家自己理解吧……

话说这个大佬的邻接表和我的完全不一样啊喂……

#include<bits/stdc++.h>
#define M(x,y) make_pair(x,y)
using namespace std;
int fr[],to[],nex[],v[],tl,d[];
bool b[];
void add(int x,int y,int w){
to[++tl]=y;
v[tl]=w;
nex[tl]=fr[x];
fr[x]=tl;
}
priority_queue< pair<int,int> > q;
int main(){
int n,m,x,y,z,s;
scanf("%d%d%d",&n,&m,&s);
for(int i=;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
}
for(int i=;i<=n;i++) d[i]=1e10;
d[s]=;
q.push(M(,s));
while(!q.empty()){
int x=q.top().second;
q.pop();
if(b[x]) continue;
b[x]=;
for(int i=fr[x];i;i=nex[i]){
int y=to[i],l=v[i];
if(d[y]>d[x]+l){
d[y]=d[x]+l;
q.push(M(-d[y],y));//懒得重载运算符
}
}
}
for(int i=;i<=n;i++) printf("%d ",d[i]);
return ;
}

Part 7:我为什么退役

顺便提一句,今天的正文部分到此就结束了,下面都是无关紧要的BB部分

考试过不了,考试过不了,考试过不了啊啊啊啊啊啊!!!!

6/27,这是沉重的一天,我参加了某推荐生考试,本来去了我所向往的高中之后,我就可以继续在OI的道路上越走越远的

但是,但是,我好像搞砸了。。。为了学OI和准备推荐生考试我花了很长时间,导致初中文化课没怎么复习,距离中考还有十几天,我还一点点都没有复习

本来这所高中就是重高,依靠中考很难考上,再加上我没有复习,直接原地爆炸

所以,综上这些原因导致了我OI梦的夭折,另外希望大家不要重蹈我的覆辙

最后,祝我退役快乐。。。。。

图论算法(三) 最短路SPFA算法的更多相关文章

  1. 图论-单源最短路-SPFA算法

    有关概念: 最短路问题:若在图中的每一条边都有对应的权值,求从一点到另一点之间权值和最小的路径 SPFA算法的功能是求固定起点到图中其余各点的的最短路(单源最短路径) 约定:图中不存在负权环,用邻接表 ...

  2. 最短路-SPFA算法&Floyd算法

    SPFA算法 算法复杂度 SPFA 算法是 Bellman-Ford算法 的队列优化算法的别称,通常用于求含负权边的单源最短路径,以及判负权环. SPFA一般情况复杂度是O(m)最坏情况下复杂度和朴素 ...

  3. 单源最短路——SPFA算法(Bellman-Ford算法队列优化)

    spfa的算法思想(动态逼近法):     设立一个先进先出的队列q用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路 ...

  4. 并不对劲的图论专题(三):SPFA算法的优化

    1.bzoj1489-> 这是个新套路. 我们希望找到最小的x,那么可以二分x,然后判断是否存在圈的边权的平均值小于等于x. 设圈的边权依次为w1,w2,w3,…,wk,平均值为p, 则有p= ...

  5. poj 3013 最短路SPFA算法

    POJ_3013_最短路 Big Christmas Tree Time Limit: 3000MS   Memory Limit: 131072K Total Submissions: 23630 ...

  6. 【笔记】最短路——SPFA算法

    ##算法功能 找最短路(最长路?) ##算法思想 用一个节点k更新节点i到节点j的最短路 ##邻接链表存储 基础而高效的图的存储方式 存的是单向边(无向边可以看成两条有向边) ##实现 维护节点i到源 ...

  7. 最短路 spfa 算法 && 链式前向星存图

    推荐博客  https://i.cnblogs.com/EditPosts.aspx?opt=1 http://blog.csdn.net/mcdonnell_douglas/article/deta ...

  8. 单源最短路SPFA算法

    $huaji^{233……}$模板:洛谷 P3371 #include<iostream> #include<algorithm> #include<cstdio> ...

  9. 洛谷 P1073 最优贸易 最短路+SPFA算法

    目录 题面 题目链接 题目描述 输入输出格式 输入格式 输出格式 输入输出样例 输入样例 输出样例 说明 思路 AC代码 题面 题目链接 P1073 最优贸易 题目描述 C国有 $ n $ 个大城市和 ...

随机推荐

  1. 图文详解压力测试工具JMeter的安装与使用

    压力测试是目前大型网站系统的设计和开发中不可或缺的环节,通常会和容量预估等工作结合在一起,穿插在系统开发的不同方案.压力测试可以帮助我们及时发现系统的性能短板和瓶颈问题,在这个基础在上再进行针对性的性 ...

  2. 题解 洛谷 P4112 【[HEOI2015]最短不公共子串】

    给定两个字符串\(A\)和\(B\),我们需要找出一个串,其在\(A\)中出现且不在\(B\)中出现,这个串为子串或者子序列,求在每种情况下,该串的最短长度. 考虑到后缀自动机可以识别一个字符串的所有 ...

  3. 痞子衡嵌入式:恩智浦i.MX RTxxx系列MCU特性那些事(1)- 概览

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦i.MX RTxxx系列MCU的基本特性. 恩智浦半导体于2017年开始推出的i.MX RT系列重新定义了MCU,其第一款芯片i. ...

  4. Android:自定义BaseActivity基类

    使用BaseActivity可以封装一些重复代码例如设置标题栏颜色,封装一些工具类... 主要功能: 封装Toast 新建一个BaseActivity继承自Activity package com.o ...

  5. JS的执行上下文

    定义 执行上下文时是代码执行时的环境,JS代码在运行前进行编译,那么会生成两部分,一部分是可执行的代码,而另一部分则是执行上下文. 发展 执行上下文所包含的内容是在不断的变化的.它主要分为了三个不同的 ...

  6. 解决移动端rem加载瞬间页面错乱的方法(放大或者缩小)

    移动端布局有很多种,这里我们最常使用到rem+百分比的布局方式(高度/字体设置rem单位,宽度设置百分比)来处理屏幕兼容,这种方法在兼容上是比较不错的,可以使得字体以及整体适应各种大小的屏幕,可以解决 ...

  7. jenkins初学部分笔记网站

    https://www.cnblogs.com/wfd360/p/11314697.html 自动化部署详细教程 https://blog.csdn.net/weixin_41948075/artic ...

  8. (一)python 格式化 excel 格式

    需求: 客户通过 sftp 上传了一个 poc测试的 excel文件, 下到 云桌面 查看,发现一堆格式问题, 怎么办呢? 公司又不允许 吧文件下载到本地处理, 只能在 服务器上进行处理. 一堆的类型 ...

  9. matplotlib基础汇总_02

    设置plot的风格和样式 点和线的样式 颜色 参数color或c 五种定义颜色值的方式 别名 color='r' 合法的HTML颜色名 color = 'red' HTML十六进制字符串 color ...

  10. ElasticJob和SpringBoot

    本文以在SpringBoot下集成ElasticJob的方式对其进行浅析,仅仅是简单使用,不涉及源码级别研究. 事先必备: 注册中心——zookeeper 简略结构: 代码目录结构: ├─.idea ...