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

这篇具有很好参考价值的文章主要介绍了第十八章 SPFA算法以及负环问题(利用dijkstra推导出该算法,超级详细!!)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、dijkstra算法的弊端

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

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

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

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

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

二、dijkstra算法的优化

1、SPFA算法

(1)算法思路:

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

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

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

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

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

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

这里需要注意两个细节:

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

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

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

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

我们让第一个点入队,然后让这个点去松弛剩下的点,那么松弛成功的应该是1的邻接点:2,3,5。(证明请看dijkstra算法的文章)
同时也说明,我们此时依旧只需要去松弛邻接点。
此时队列中的元素如下:
第十八章 SPFA算法以及负环问题(利用dijkstra推导出该算法,超级详细!!)

那么接着我们让2出队,去继续松弛。
假设我们2的邻接点都松弛成功了。
第十八章 SPFA算法以及负环问题(利用dijkstra推导出该算法,超级详细!!)
那么此时的队列中,我们惊奇的发现,5进队了两次。好,问题来了,

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

我们依旧采取前面两篇文章的风格,不主观判断,我们客观分析,那么客观分析的依据就是公式:
第十八章 SPFA算法以及负环问题(利用dijkstra推导出该算法,超级详细!!)
我们先来分析一下:
我们松弛过后,改变的数据是对应点的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)算法模板:

问题:

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

模板:
#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算法以及负环问题(利用dijkstra推导出该算法,超级详细!!)

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

答案是不能的。

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

三、SFPA解决负环问题:

1、什么是负环?

第十八章 SPFA算法以及负环问题(利用dijkstra推导出该算法,超级详细!!)
上述这个环每走一圈,最短路 - 2 。因此,我们可以围着这个圈旋转无数次,那么我们的最短路就到负无穷了。因此,这种情况是没有最短路的。

2、如何判断负环?

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

为什么?

第十八章 SPFA算法以及负环问题(利用dijkstra推导出该算法,超级详细!!)
假设这是一个图,那么这个环是每走一圈都会回到C点,而每走一圈,路程+4。那么从最上面的点,到最后一个点的最短路一定是不走这个环的。所以我们的最短路是一条线。
第十八章 SPFA算法以及负环问题(利用dijkstra推导出该算法,超级详细!!)
而一条线上,n个点之间的边是n-1。所以图中所有最短路的边数最多是n-1。如果存在负环,那么它为了减少路程必定会进环,因此此时的边数必定是大于等于n的。

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

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

3、细节处理:

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

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

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

4、模板:

(1)问题:

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

(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)分析:

第十八章 SPFA算法以及负环问题(利用dijkstra推导出该算法,超级详细!!)
由于我们一开始入队一个,就是找一个点的最短路,入队n个点那么就是找n个点的最短路,所以此时的最坏时间复杂度是:O(mn2)。这个方式是相当低效的。文章来源地址https://www.toymoban.com/news/detail-430937.html

到了这里,关于第十八章 SPFA算法以及负环问题(利用dijkstra推导出该算法,超级详细!!)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【新版系统架构】第十八章-安全架构设计理论与实践

    信息系统安全设计重点考虑:系统安全保障体系,信息安全体系架构 系统安全保障体系: 安全区域策略的确定,根据安全区域的划分,主管部门应制定针对性的安全策略 统一配置和管理防病毒系统,主管部门应当建立整体防御策略,以实现统一的配置和管理 网络安全管理,

    2024年02月13日
    浏览(46)
  • 华为HCIE课堂笔记第十八章 SR技术

    SR可以通过控制器实现集中算路,并根据业务的不同下发不同的路径给到头端设备,头端设备将路径标签通过标签栈的形式压入到报文中,沿途的设备不需要维护路径信息,仅按照标签栈中的栈顶进行报文转发即可。 控制平面:扩展后的IGP协议,通过IGP分发标签 转发平面:基

    2024年02月19日
    浏览(40)
  • Hotspot源码解析-第十八章-元空间的创建与分配

    元空间就是从C堆中划出来的一片完整的区域,为了提升元数据的内存分配效率,又把元空间按若干个chunk内存块管理起来,其中chunk块又分为已使用和空间两种类型,并分别用VirtualSpaceList和ChunkManager来管理,chunk内存块之间以链表的形式关联起来,同时为了满足不同元数据占

    2024年02月01日
    浏览(48)
  • 【图论】SPFA求负环

    算法提高课笔记 负环 :环上权值之和是负数 求负环的常用方法 基于SPFA 统计每个点入队次数,如果某个点入队n次,则说明存在负环(完全等价于Bellman-Ford算法) 统计当前每个点的最短路中所包含的边数,如果某点的最短路包含的边数大于等于n,则说明存在负环 玄学结论:

    2024年02月09日
    浏览(37)
  • Docker第十八章 : 构建您的第一个Java镜像

    第十八章 : 构建您的第一个Java镜像 本章知识点: 介绍构建java镜像的基本步骤,以及如何通过阿里云效和阿里云容器镜像服务,构建您的第一个Java镜像。 导读 Java入门指南教您如何使用Docker创建容器化的Spring Boot应用程序。在本模块中,您将学习如何: 使用Maven克隆并运行

    2024年02月22日
    浏览(52)
  • 第十八章_Redis缓存预热+缓存雪崩+缓存击穿+缓存穿透

    缓存预热 缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据。 可以通过@PostConstruct初始化白名单数据 缓存雪崩 发生  redis主机挂了,Redis 全盘崩溃

    2024年02月07日
    浏览(44)
  • 第十八届智能车之PID算法以及上位机调节

    当前正在备战第十八届智能车,记录一下学习和实践的过程,这一篇主要是讲pid算法以及调试。 PID即:Proportional(比例)、Integral(积分)、Differential(微分)的缩写。 PID是经典的闭环控制算法,具有原理简单,易于实现,适用面广,控制参数相互独立,参数的选定比较简单

    2024年02月06日
    浏览(31)
  • 斯坦福 Stats60:21 世纪的统计学:第十五章到第十八章

    原文: statsthinking21.github.io/statsthinking21-core-site/comparing-means.html 译者:飞龙 协议:CC BY-NC-SA 4.0 我们已经遇到了许多情况,我们想要询问样本均值的问题。在本章中,我们将更深入地探讨我们可以比较不同组均值的各种方法。 我们可能想要询问均值是否具有特定值的最简单的

    2024年01月16日
    浏览(48)
  • C++ Primer第五版_第十八章习题答案(11~20)

    练习18.11 为什么 what 函数不应该抛出异常? what中如果抛出异常,需要try catch捕获,再调用what,一直循环,直达内存耗尽。 练习18.12 将你为之前各章练习编写的程序放置在各自的命名空间中。也就是说,命名空间chapter15包含Query程序的代码,命名空间chapter10包含TextQuery的代码

    2024年02月06日
    浏览(43)
  • 【Rust】Rust学习 第十八章模式用来匹配值的结构

    模式是 Rust 中特殊的语法,它用来匹配类型中的结构,无论类型是简单还是复杂。结合使用模式和  match  表达式以及其他结构可以提供更多对程序控制流的支配权。模式由如下一些内容组合而成: 字面值 解构的数组、枚举、结构体或者元组 变量 通配符 占位符 这些部分描

    2024年02月11日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包