第十八章 SPFA算法以及负环问题(利用dijkstra推导出该算法,超级详细!!)

article/2025/10/23 13:15:25

第十八章 SPFA算法以及负环问题

  • 一、dijkstra算法的弊端
  • 二、dijkstra算法的优化
    • 1、SPFA算法
      • (1)算法思路:
      • (2)算法模板:
        • 问题:
        • 模板:
        • 逐行分析:
  • 三、SFPA解决负环问题:
    • 1、什么是负环?
    • 2、如何判断负环?
    • 3、细节处理:
    • 4、模板:
      • (1)问题:
      • (2)模板:
      • (3)分析:

一、dijkstra算法的弊端

我们回顾一下之前的dijkstra算法的证明过程。如果大家没看过之前的dijkstra算法的简易证明的话,作者在这里建议先去看一下。
传送门:
第十六章 Dijkstra算法的讲解以及证明(与众不同的通俗证明)
那么假设你已经看过这篇文章,我们发现,我们将每次松弛操作后的最小距离定义为已经确定的最短路。那么我们的前提就是:
在这里插入图片描述
该公式中的W是正数。那么我们利用一下极限思想,假设存在一个W是负无穷。那么右端的值就是负无穷。此时我们就还能够更新我们的最短路。

因此当我们的图中存在负权边的时候,我们的dijkstra算法是不一定成立的。

那么我们如何基于这种情况对于dijkstra算法进行优化呢?

我们下列的优化是基于之前的优先队列优化过的dijkstra算法,所以如果屏幕前的同学不懂优先队列优化的dijkstra算法的话,建议再去看一下前面的一篇文章:

第十七章 优先队列优化Dijkstra算法

二、dijkstra算法的优化

1、SPFA算法

(1)算法思路:

作者不会在这里直接甩出一个算法让大家硬背。我们看看我们能不能自己推导出SPFA算法呢?

由于负权边的存在,我们不能再保证每次松弛过后的最小值是最短路,解决这个事情很简单。既然你不一定是最短路,那我就接着松弛你喽。只要你无法再松弛了,那必定是最短路了。

既然我们无法保证最小的那个是最短路了,那我们还有必要去选出最小值吗?

答案是没必要的,因为选出最小值不仅无法做到确定最值,还白白增加了时间复杂度。所以我们不再需要优先队列存储,只需要最简单的队列即可。

所以我们的第一个改变就是:不再选出最小值。

接着,那我们怎么知道所有点都无法再继续松弛了呢?此时,我们发现,我们之前在实现优化的dijkstra算法的时候,我们只让松弛过后的点进队列。所以,如果不再入队了,那么就说明这个点无法松弛了,也就是说找到了最短路。因此,当队列为空的时候,就说明已经所有点的最短路都找到了。

这里需要注意两个细节:

出队之后的点还能入队吗?

在我们优化dijkstra的算法中,我们出队的是最小点,此时这个点的最短路确定了,所以不再入队。但是,现在来看,我们的点即使出队了,但依旧无法确定是最小路径,因为你不知道这个点能够利用当前的路再去松弛,因此,出队的点依旧能够再次进队。

接着我们看下面这个例子,再去解决下一个细节。

在这里插入图片描述

我们让第一个点入队,然后让这个点去松弛剩下的点,那么松弛成功的应该是1的邻接点:2,3,5。(证明请看dijkstra算法的文章)
同时也说明,我们此时依旧只需要去松弛邻接点。
此时队列中的元素如下:
在这里插入图片描述

那么接着我们让2出队,去继续松弛。
假设我们2的邻接点都松弛成功了。
在这里插入图片描述
那么此时的队列中,我们惊奇的发现,5进队了两次。好,问题来了,

一个点有没有必要在队列中出现N次?

我们依旧采取前面两篇文章的风格,不主观判断,我们客观分析,那么客观分析的依据就是公式:
在这里插入图片描述
我们先来分析一下:
我们松弛过后,改变的数据是对应点的dis数组,第一个改变后,dis[5]=C1 改变了,当我们再次松弛成功后,dis[5]=C2又发生了改变。
那么当我们第一个d[5]出队列的时候,用的dis[5]是哪个值?答案肯定是C2。因为dis[5]对应的就是一块空间。只不过当2出队前,是C1。2出队后,是C2。也就是说,我们第一个5出队的时候,用的是C2这个数据。
我们第一个5出队的时候,利用这个公式d[U]<=C2+w去松弛了一遍。然后,轮到我们第二个5出队的时候,又利用d[U]<=C2+w去松弛一遍。即两次我们重复了。

因此,某个点无需多个同时存在于队列中。

最后,我们总结一下我们的优化思路:

不断的松弛邻接点,松弛成功的点入队,直到队列为空,说明最短路已经全部找到,其中有两个细节:一是出队的点可以再次入队,二是某个点只需要在队列中同时存在一个。

那么这就是SPFA的算法逻辑!!

恭喜你,自己优化出了一个新的算法!

如果屏幕前的你出生够早,这个算法必定是由你命名的(^ _ ^)。

(2)算法模板:

问题:

在这里插入图片描述

模板:

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=1e5+10;
int h[N],e[N],ne[N],w[N],idx;
int dis[N];
bool st[N];
int n,m;
void add(int x,int y,int z)
{e[idx]=y,ne[idx]=h[x],w[idx]=z,h[x]=idx++;
}
bool SPFA()
{memset(dis,0x3f,sizeof dis);queue<int>q;q.push(1),dis[1]=0;st[1]=true;while(!q.empty()){int t=q.front();q.pop();st[t]=false;for(int i=h[t];i!=-1;i=ne[i]){if(dis[e[i]]>dis[t]+w[i]){dis[e[i]]=dis[t]+w[i];if(!st[e[i]])q.push(e[i]);st[e[i]]=true;}}}if(dis[n]>0x3f3f3f3f/2)return false;else return true;   
}int main()
{memset(h,-1,sizeof h);cin>>n>>m;for(int i=0;i<m;i++){int a,b,c;scanf("%d%d%d",&a,&b,&c);add(a,b,c);}if(SPFA())cout<<dis[n]<<endl;else puts("impossible");
}

逐行分析:

在这里插入图片描述

SPFA的平均时间复杂度是O(Km),m为边数,K为每个点的平均入队次数。这是相当高效的!
该效率甚至超过了堆优化版的dijkstra算法。但是,这个算法能够取代dijkstra吗?

答案是不能的。

在最坏情况下,即每个点入队n次。**那么此时的时间复杂度是O(mn)**这个时间复杂度就是相当高了,而堆优化的dijkstra是O(mlogn)。

三、SFPA解决负环问题:

1、什么是负环?

在这里插入图片描述
上述这个环每走一圈,最短路 - 2 。因此,我们可以围着这个圈旋转无数次,那么我们的最短路就到负无穷了。因此,这种情况是没有最短路的。

2、如何判断负环?

存在最短路的话,一定是没有与源点相通的负环的,那么我们此时从源点引出的最短路经过的边长最多为n-1。

为什么?

在这里插入图片描述
假设这是一个图,那么这个环是每走一圈都会回到C点,而每走一圈,路程+4。那么从最上面的点,到最后一个点的最短路一定是不走这个环的。所以我们的最短路是一条线。
在这里插入图片描述
而一条线上,n个点之间的边是n-1。所以图中所有最短路的边数最多是n-1。如果存在负环,那么它为了减少路程必定会进环,因此此时的边数必定是大于等于n的。

而这就是我们判断负环的依据:最短路径上经过的边大于等于n

因此,我们只需要在SPFA的算法中,加上一个数组,来存储最短路的边数即可。

3、细节处理:

在这里插入图片描述

这个图中的确存在负环,但是我们的S点出发的最短路不经过这个负环。那么我们的算法在这种情况下就失效了。因此。我们让所有点都进队,此时负环中的点必定也会进队

因此必定会找到这个负环。我们把dis数组全部初始化为正无穷。那么当遇到一个负权边的时候,必定能够松弛成功,因为w是负的,dis又都是正无穷。也就是说我们的dis数组初始值是多少不重要,因为不管你初始化为多少,我给你减去一个数,你必定减小,必定松弛成功。接着它就会围绕这个环不断地转,当走过的边数是n的时候,就是负环了。

4、模板:

(1)问题:

在这里插入图片描述

(2)模板:

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=1e5+10;
int h[N],e[2*N],ne[2*N],w[2*N],idx;
bool st[N];
int dis[N],cnt[N];
int n,m;
void add(int x,int y, int d)
{e[idx]=y,ne[idx]=h[x],w[idx]=d,h[x]=idx++;
}bool spfa()
{memset(dis,0x3f,sizeof dis);queue<int>q;for (int i = 1; i <= n; i ++ ){st[i] = true;q.push(i);}while(!q.empty()){auto top=q.front();q.pop();st[top]=false;for(int i=h[top];i!=-1;i=ne[i]){if(dis[e[i]]>dis[top]+w[i]){dis[e[i]]=dis[top]+w[i];cnt[e[i]]=cnt[top]+1;if(cnt[e[i]]>=n)return true;if(!st[e[i]]){q.push(e[i]);st[e[i]]=true;}}}}return false;
}
int main()
{memset(h,-1,sizeof h);cin>>n>>m;while(m--){int a,b,c;scanf("%d%d%d",&a,&b,&c);add(a,b,c);}if(spfa())puts("Yes");else puts("No");}

(3)分析:

在这里插入图片描述
由于我们一开始入队一个,就是找一个点的最短路,入队n个点那么就是找n个点的最短路,所以此时的最坏时间复杂度是:O(mn2)。这个方式是相当低效的。


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

相关文章

SPFA算法理论体系终极论证

SPFA相关详细论证 历史事件Bellman-Ford算法简述和证明SPFA的正确代码段凡丁的贡献正确复杂度分析 历史事件 众所周知&#xff0c;SPFA是一种对Bellman-Ford算法的优化。国内业界首次提出是1994年西南交通大学的段凡丁在学报上发表的论文。但实际上早在1956年Bellman-Ford算法…

spfa判定负环

算法思想&#xff1a; 我们用数组记录每个结点的最短路径估计值&#xff0c;用邻接表来存储图G。 采取的方法是动态逼近法&#xff1a; 1、设立一个先进先出的队列用来保存待优化的结点。 2、优化时每次取出队首结点u&#xff0c;并且用u点当前的最短路径估计值对离开u点所指向…

SPFA算法详解——判断负权环

算法核心代码实现负权环判断负权环 根据松弛次数根据最短路径上的死循环 SPFA&#xff08;Shortest Path Faster Algorithm&#xff09;&#xff08;队列优化&#xff09;算法是求单源最短路径的一种算法。它是在Bellman-ford算法的基础上加上一个队列优化&#xff0c;减少了冗…

(算法基础)SPFA算法

适用情景 SPFA算法一般运用在求最短路问题当中的单源最短路中存在负权边的情况&#xff08;正权边如果出题人没有特别恶心去卡的话&#xff0c;也是ok的&#xff09;&#xff0c;SPFA算法还可以去判断图中是否存在负环。 时间复杂度 一般来讲是O(m)&#xff0c;m就是图当中的边…

透析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…