(算法基础)SPFA算法

article/2025/10/23 15:29:16

适用情景

  1. SPFA算法一般运用在求最短路问题当中的单源最短路中存在负权边的情况(正权边如果出题人没有特别恶心去卡的话,也是ok的),SPFA算法还可以去判断图中是否存在负环


时间复杂度

  1. 一般来讲是O(m),m就是图当中的边数,最坏的情况是O(nm)。


算法解释 I (SPFA求单源最短路)

  1. 首先是对于图的存储,可以用邻接表去存储图,也可以用邻接矩阵去存储图。这边就不像之前的Bellman-Ford算法,那个算法的话对于边的存储十分的随意,因为每一次循环的话,我只要保证能够遍历到每一条边就可以。而在SPFA算法当中,不能去随便的遍历所有边,详情见下。还有add函数的话也是会的吧,值得注意的是这边有个小坑的地方,对于h的初始化-1,这一步的操作必须放在插入边之前,这看似是小儿科,但是这个错误我感觉还是很容易犯。

#define N 100010
int h[N];
int e[N];
int ne[N];
int w[N];
int idx;
void add(int a, int b, int c)
{e[idx]=b;ne[idx]=h[a];h[a]=idx;w[idx]=c;idx++;
}
memset(h,0xff,sizeof(h));
  1. 然后当然也得创建一个dist数组,用来存储每一个点到一号点之间的最短路距离,在初始化的时候,默认每一个点都是无穷大(当然,一号点除外)。

int dist[N];
memset(dist,0x3f,sizeof(dist));
dist[1]=0;
  1. 还要创建一个队列,这个队列就是来记录当前有哪些点到一号点的最短路距离已经是被更新过了的那些最短路距离已经被更新过了的点就是要放到队列里面然后这些受到更新的点再去更新其他点的最短路距离,然后那些点等会儿也会进到队列里面,如此往复。当然在最初始状态,很容易就知道一号点到一号点的最短路距离就是0,因此可以理解成一号点已经被更新过了,所以最开始一号点先入队。

int queue[N];
int hh;
int tt;
queue[0]=1;
  1. 那等会儿在具体的算法过程中,该如何去判断有哪些点已经是被更新过了(或者说通俗来讲的话,就是说有哪些点已经是在这个队列里面),这时候你只需要去再创建一个数组st,就是做标记作用,标记一下有哪些点已经是在队列里面。当然,一号点的话到一号点的距离就是0,所以说也相当于是更新过了它的最短路距离,所以在最初始状态的话,我们知道一号点是入队了的,所以在这边标记的时候,得给一号点标记一下,说明他此时已经在队列里面了。

int st[N];
st[1]=1;
  1. 必须要弄懂的是:SPFA算法的精髓就在于:一旦我该点的最短路被更新了是吧(相当于别人恶心了我,那我也就去恶心别人,并且恶心一遍接触到的人之后,我就悄咪咪滚蛋了,队头元素开始去恶心别人),我就去更新我所连接到的那些图当中的点的最短路距离,其实就是去判断dist[ b ]与dist[ a ]+c (从a指向b,权重为c)。遍历一边以该点为出发点的所有边,遍历一圈之后,该点就从队头弹出,然后在遍历的过程当中,如果有点的最短路距离被我更新了,那么此时此刻他如果不在队列里面的话,那就入队,在的话那就不说了。就这样循环往复,到最后队列为空为止。

while(hh<=tt)
{int num=queue[hh];hh++;st[num]=0;for (int i=h[num];i!=-1;i=ne[i]){int k=e[i];if (dist[k]>dist[num]+w[i]){dist[k]=dist[num]+w[i];if (st[k]==0){st[k]=1;queue[++tt]=k;}}}
}
  1. 然后在最后,如果说等到队列都空了,此时此刻dist[ n ]还是不动如山如之前初始化一模一样,这就说明是从一号点是走不到n号点的

if (dist[n]==0x3f3f3f3f)
{printf("impossible\n");
}
else
{printf("%d\n",dist[n]);
}

例题

来源:AcWing

851. spfa求最短路 - AcWing题库

#include <stdio.h>
#include <string.h>
#define N 100010
int n,m;
int h[N];
int e[N];
int ne[N];
int w[N];
int idx;
void add(int a, int b, int c)
{e[idx]=b;ne[idx]=h[a];h[a]=idx;w[idx]=c;idx++;
}
int dist[N];
int queue[N];
int hh;
int tt;
int st[N];
int main()
{memset(h,0xff,sizeof(h));memset(dist,0x3f,sizeof(dist));dist[1]=0;queue[0]=1;st[1]=1;scanf("%d %d",&n,&m);int a,b,c;while(m--){scanf("%d %d %d",&a,&b,&c);add(a,b,c);}while(hh<=tt){int num=queue[hh];hh++;st[num]=0;for (int i=h[num];i!=-1;i=ne[i]){int k=e[i];if (dist[k]>dist[num]+w[i]){dist[k]=dist[num]+w[i];if (st[k]==0){st[k]=1;queue[++tt]=k;}}}}if (dist[n]==0x3f3f3f3f){printf("impossible\n");}else{printf("%d\n",dist[n]);}return 0;
}

算法解释 II (SPFA判断图中是否有负环)

  1. 首先必须弄清楚负权边与负环的概念,负环是一种特殊的负权边,也就是说这条边的权重是负的,并且从自己这个点出发,结果还是指向自己这个点。

  1. SPFA算法的精髓在于:如果受到更新,就入队;然后等着去更新别人,遍历自己势力范围一圈后,出队;其他点也一样,如果受到更新,就入队

  1. 图的邻接表存储,道理还是一样的

#define N 2020
#define M 10010
int h[N];
int e[M];
int ne[M];
int w[M];
int idx;
memset(h,0xff,sizeof(h));
void add(int a, int b, int c)
{e[idx]=b;ne[idx]=h[a];h[a]=idx;w[idx]=c;idx++;
}
  1. 这边与之前的SPFA算法求最短路有一些区别,首先对于队列,在初始状态就把所有的点全部放到队列里面(因为如果只放一个点的话,可能这个负环不在以该点为起点的路途网中)。那既然在最初状态所有的点都在队列当中,所以说对于标记数组st而言,所有的元素值最初都为1

int st[N];
int queue[10000000];
int hh;
int tt=-1;
for (int i=1;i<=n;i++)
{queue[++tt]=i;st[i]=1
}
  1. 由于现在每一个点好像似乎都成为了“起点”,所以dist数组中的值最初全为0

int dist[N];
  1. 这边还得额外定义一个cnt数组,这个数组表示在以某一点为终点的最短路当中整个路当中的边的总数量,由于现在每一个点好像似乎都成为了起点,所以在最短路当中可以理解为从自己走向自己,那么边数为0,所以一开始cnt的值全为0.

int cnt[N];
  1. 然后接下来就是通过循环去执行SPFA算法的精髓,什么时候第一个if条件会进去呢,只要这时候存在负权边(a, b, c c<0 ),这时候就会去更新dist[ b ]。与此同时,最短路走通,在原先的基础之上又多加了一条边, cnt[ b ] = cnt [ a ] + 1 。如果单单是负权边倒还问题不大,如果说这时候是负环,也就意味着当我出队,但是我又把自己给更新了,于是乎我又马上又入队了,然后到时候我又出队,然后我又入队,如此循环往复,但是每一次的话cnt都会加1,直到有一天cnt大于n了,说明在最短路当中从起点到该点有n条边,那么就意味着有n+1个点,那么就意味着必然存在环,最短路,最短路,正环不可能,也就是说存在着负环

while(hh<=tt)
{int num=queue[hh];st[num]=0;hh++;for (int i=h[num];i!=-1;i=ne[i]){int k=e[i];if (dist[k]>dist[num]+w[i]){dist[k]=dist[num]+w[i];cnt[k]=cnt[num]+1;if (cnt[k]>n){printf("Yes\n");return 0;}if (st[k]==0){st[k]=1;queue[++tt]=k;    }}}
}
printf("No\n");

例题

来源:AcWing

852. spfa判断负环 - AcWing题库

#include <stdio.h>
#include <string.h>
#define N 2020
#define M 10010
int n,m;
int h[N];
int e[M];
int ne[M];
int w[M];
int idx;
int cnt[N];
void add(int a, int b, int c)
{e[idx]=b;ne[idx]=h[a];h[a]=idx;w[idx]=c;idx++;
}
int st[N];
int queue[10000000];
int hh;
int tt=-1;
int dist[N];
int main()
{scanf("%d %d",&n,&m);memset(h,0xff,sizeof(h));int a,b,c;while(m--){scanf("%d %d %d",&a,&b,&c);add(a,b,c);}for (int i=1;i<=n;i++){queue[++tt]=i;st[i]=1;}while(hh<=tt){int num=queue[hh];st[num]=0;hh++;for (int i=h[num];i!=-1;i=ne[i]){int k=e[i];if (dist[k]>dist[num]+w[i]){dist[k]=dist[num]+w[i];cnt[k]=cnt[num]+1;if (cnt[k]>n){printf("Yes\n");return 0;}if (st[k]==0){st[k]=1;queue[++tt]=k;    }}}}printf("No\n");return 0;
}


http://chatgpt.dhexx.cn/article/If6lnP3P.shtml

相关文章

透析SPFA算法(图例讲解)

SPFA算法是Bellman-Ford的队列优化&#xff0c;所以先介绍Bellman-Ford算法。 Dijkstra算法是处理单源最短路径的有效算法&#xff0c;但它局限于边的权值非负的情况&#xff0c;若图中出现权值为负的边&#xff0c;Dijkstra算法就会失效&#xff0c;求出的最短路径就可能是错的…

SPFA 算法详解( 强大图解,不会都难!)

适用范围&#xff1a;给定的图存在负权边&#xff0c;这时类似Dijkstra等算法便没有了用武之地&#xff0c;而Bellman-Ford算法的复杂度又过高&#xff0c;SPFA算法便派上用场了。 我们约定有向加权图G不存在负权回路&#xff0c;即最短路径一定存在。当然&#xff0c;我们可以…

OSPF NSSA区域路由的计算过程与FA值实验

概述&#xff1a; NSSA区域中不存在5类LSA&#xff0c;但是为了描述区域外的路由条目&#xff0c;则用7类LSA代替5类LSA。 7类LSA仅在NSSA区域中存在&#xff0c;当7类LSA离开NSSA区域进入普通区域时&#xff0c;可以转化为5类LSA。 NSSA-LSA报文格式和5类LSA相同&#xff1…

求不规则立方体表面积java_不规则立方体体积计算

展开全部 计算不规则的立体图形体积可62616964757a686964616fe78988e69d8331333431373866以把这个物体放入水中&#xff0c;用现在容积-未放入物体的容积就是体积或用放入物体后高-未放入物体*长*宽(1升1立方分米&#xff1b;1毫升1立方厘米)。 体积的国际单位制是立方米。一件…

冠层分析法(VCP)提取叶面积指数

文章目录 一、简介二、代码实现三、小结参考文献一、简介 该方法最早是由Omasa等人提出,是根据激光雷达光束与树木的接触频率来计算光束截获概率。总结来讲具体过程分为三步: 1、 从三维体素模型网格里判断是否有点,有点的网格标记为1,没点的网格标记为0,没点的网格表明冠…

SPFA例题

引入 SPFA的核心思想是优化dist[a]min(dist[a],dist[b]w)这个式子&#xff1b; 不难想到&#xff0c;如果我们想要优化dist[a]&#xff0c;那么只能从dist[b]w这个式子转移过来&#xff1b; 也就是说&#xff0c;只有dist[b]变小了&#xff0c;才有可能优化dist[a]&#xff…

OSPF中的SPF计算

OSPF域内路由计算&#xff1a;SPF计算 在1类LSA和2类LSA中&#xff0c;包括了拓扑信息和路由信息 Phase 1 &#xff1a;根据1类LSA中的P2P、TransNet、2类LSA&#xff0c;构建SPF树 Phase 2&#xff1a;根据1类LSA中的Stub、2类LSA&#xff0c;计算最优路由 左边是拓扑图&am…

聊一聊有限体积法

导读&#xff1a;有限体积法的工作原理。 一、求解对象 首先提一下我们的主角Navier-Stokes方程&#xff1a; 为了能够通过SIMPLE算法求解Navier-Stokes方程&#xff0c;我们需要将该方程改写为下列矩阵形式的方程&#xff1a; 其中为了系数矩阵&#xff0c;是每个网格未知…

IDEA中新导入的项目找不到maven project解决办法

刚导入的项目&#xff0c;导入进来之后&#xff0c;找不到maven project&#xff0c;idea打开view ->tool windows下也没有maven模块。查看了setting下plugins中maven插件也在。最后发现项目在导入过程中&#xff0c;因为选择的原因&#xff0c;没有指定位maven工程&#xf…

win10删除文件提示'找不到该项目'

win10系统下删除文件时出现了"找不到该项目"问题 遇到一个问题: win10系统下删除文件时出现了"找不到该项目"问题 百度搜索后得到 DEL /F /A /Q \\?\%1 RD /S /Q \\?\%1 这两行dos命令 创建(.txt)文件后写入这两行命令,保存,修改(.txt)至(.bat) 最后…

IDEA项目下不显示target目录或者target目录不完整没有新添加的资源

1.首先解决不显示target目录的问题 需要知道:如果不是maven工程,是没有target目录的,其次编译后才会生成target目录. 普通项目会生成out目录 a>如果设置过隐藏target目录 只需要找到settings-->Editor-->File Type-->Ignore Files and Folder删除该项 b>如果…

项目启动报错,找不到文件

java.io.FileNotFoundExceptionE:\software\apache-maven-3.6.1-bin\apache-maven-3.6.1\localrepository\xalan\xalan\2.7.0\xercesImpl.jar (系统找不到指定的文件。) java.io.FileNotFoundExceptionE:\software\apache-maven-3.6.1-bin\apache-maven-3.6.1\localrepository\…

idea项目类存在却找不到

这种情况&#xff0c;我真是奇了怪了&#xff0c;服了。我试了一下&#xff0c;试出了解决方案&#xff1a;将这个类移到同级另一个包&#xff0c;看见不报错了&#xff0c;再移回去。属于给idea虚晃一枪&#xff0c;再运行…emmmmm也还是报错的。于是不断的删iml文件&#xff…

Windows 删除”找不到该项目”文件夹

问题根源&#xff1a; 使用不可显示ASCII字符或采用UNICODE字符方法创建的文件或文件夹&#xff1b; 名称中含有..等特殊符号文件或文件夹名称不符合Windows命名规范或建立空格目录名创建的文件或文件夹&#xff1b; 使用下载工具创建的文件夹,在未下载完成前自行删除文件或…

idea找不到项目目录

有时候idea拉取项目时出现没有目录&#xff0c;src.main等等&#xff0c;下面就来带大家找到项目目录并显示出来 首先我们打开文件&#xff08;在左上角——找到项目结构&#xff09; 然后我们找到模块——点击左边那个加号,然后选择自己的项目目录。再选择一下jdk版本&#x…

【常见问题】vs 找不到导入的项目,请确认import声明

问题描述 当复制人家的vs项目文件到自己电脑上进行调试&#xff0c;重新生成解决方案失败&#xff0c;并报如下错误。 解决方法 1.分析报错内容&#xff0c;出现问题文件为vcxproj文件。 2.找到vcxproj文件&#xff0c;用记事本打开。 3.CTRLF搜索“Import”&#xff0c…

无法删除提示找不到该项目,该项目不在C:\users\?win10无法强力删除文件?

无法删除提示找不到该项目,该项目不在C:\users\?win10无法强力删除文件? 新建个TXT &#xff0c;把下边的代码粘贴过去&#xff1a; DEL /F /A /Q \\?\%1 RD /S /Q \\?\%1 保存之后把名字改成&#xff1a;Delete.bat 然后把要删除的文件夹拖到这个文件上&#xff0c;即可删…

git拉取项目报错:找不到项目 Repository not found

这段时间换了新公司&#xff0c;不想每天背着电脑跑来跑去了&#xff0c;就用了公司配发的电脑&#xff0c;前两天想用Git拉取公司的项目的时候却碰见问题了&#xff0c;无论是Git命令行拉取还是使用idea拉取&#xff0c;都会报错找不到项目 但是在浏览器使用链接是可以访问到项…

问题:找不到该项目,该项目不在指定目录下.......请确认位置......

参考网址&#xff1a;http://bbs.kafan.cn/thread-1398433-1-1.html 1.把下面的代码复制粘贴到一新建的txt记事本文档中&#xff0c;并另存为del.bat文件&#xff08;或者你喜欢的名字&#xff09;&#xff0c;注意扩展名为批处理文件bat&#xff1b; DEL /F /A /Q \\?\%1 RD …

windows删文件:找不到该项目,该项目不在xx中,请确认位置,然后重试 的解决方案

起因是手贱 打错了 搞得生成了一个 以.结尾的文件&#xff0c;删又删除不了&#xff0c;莫名其妙&#xff0c;然后 git push 也出问题&#xff1b; 删除就弹出类似下面的提示&#xff1b;删除上层文件夹也没用&#xff1b; 网上搜了一堆解决办法&#xff0c;说什么建立一个 ba…