我可能要退役了……

退役之前,写一篇和我一样悲惨的算法: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. 手把手带你玩转 DialogFragment

    前言 本文已经收录到我的 Github 个人博客,欢迎大佬们光临寒舍: 我的 GIthub 博客 思维导图 一.为什么要学习 DialogFragment 你还在用 Dialog 吗? 你还在经常烦恼 ...

  2. Python API 操作Hadoop hdfs详解

    1:安装 由于是windows环境(linux其实也一样),只要有pip或者setup_install安装起来都是很方便的 >pip install hdfs 2:Client——创建集群连接 ...

  3. 缓存利器、Lua模块下的共享内存

    上一节讲到了worker进程的共享内存,它利用丰富的指令使数据的缓存操作变得非常简单,但它也存在一些缺点. 1.worker进程之间会有锁竞争,在高并发的情况下会增加性能开销.2.只支持Lua布尔值. ...

  4. python的常用模块

    一.random随机数模块 使用随机数模块需要导入随机数模块import random 1.random.random() 生成[0,1)之间的随机小数 2.random.randint(a,b) 生 ...

  5. Day02_WebCrawler(网络爬虫)

    学于黑马和传智播客联合做的教学项目 感谢 黑马官网 传智播客官网 微信搜索"艺术行者",关注并回复关键词"webcrawler"获取视频和教程资料! b站在线视 ...

  6. 线程_gevent自动切换CPU协程

    import gevent def f(n): for i in range(n): print (gevent.getcurrent(), i) # gevent.getcurrent() 获取当前 ...

  7. 如何使用k3OS和Argo进行自动化边缘部署?

    本文转自边缘计算k3s社区 前 言 随着Kubernetes生态系统的发展,新的技术正在被开发出来,以实现更广泛的应用和用例.边缘计算的发展推动了对其中一些技术的需求,以实现将Kubernetes部署 ...

  8. Filebeat日志收集简单使用

    1.简略介绍 轻量型日志采集器,用于转发和汇总日志与文件. 官网: https://www.elastic.co/cn/beats/filebeat 2.本文实现的功能 3.事先必备: 至少一台Kaf ...

  9. 使用 MySQLi 和 PDO 向 MySQL 插入数据

    PHP MySQL 插入数据 使用 MySQLi 和 PDO 向 MySQL 插入数据 在创建完数据库和表后,我们可以向表中添加数据. 以下为一些语法规则: PHP 中 SQL 查询语句必须使用引号 ...

  10. 【NOIP2015】斗地主 题解(DFS+贪心)

    题目描述 牛牛最近迷上了一种叫斗地主的扑克游戏.斗地主是一种使用黑桃.红心.梅花.方片的AAA到KKK加上大小王的共545454张牌来进行的扑克牌游戏.在斗地主中,牌的大小关 系根据牌的数码表示如下: ...