最短路相关笔记

Floyd

Floyd 算法,是一种在图中求任意两点间最短路径的算法。

Floyd 算法适用于求解无负边权回路的图。

时间复杂度为 O ( n 3 ) O(n^3) O(n3),空间复杂度 O ( n 2 ) O(n^2) O(n2)

对于两点 ( i , j ) (i,j) (i,j) 之间的最短路径,有两种可能:从 i i i 直接到 j j j,或者从 i i i 经过若干结点 k k k j j j

f ( k , i , j ) f(k,i,j) f(k,i,j) 为以 k k k 为中转结点时 i i i j j j 两点间最短路径。

递推转移方程: f ( i , j , k ) = min ⁡ ( f ( k − 1 , i , j ) , f ( k − 1 , i , k ) + f ( k − 1 , k , j ) ) f(i,j,k)=\min(f(k-1,i,j),f(k-1,i,k)+f(k-1,k,j)) f(i,j,k)=min(f(k1,i,j),f(k1,i,k)+f(k1,k,j))

滚动数组可以优化为二维数组,即 f ( i , j ) f(i,j) f(i,j)

核心代码:

for(int k=1;k<=n;k++)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)f[i][j]=min(f[i][j],f[i][k]+f[k][j]);

预处理操作:

  • 将递推数组 memset 为无穷大
  • f ( i , i ) = 0 f(i,i)=0 f(i,i)=0,即自己和自己距离为 0 0 0
  • 读入 ( u , v ) (u,v) (u,v) 之间边权的同时更新 f ( u , v ) f(u,v) f(u,v),无向图无需双向赋值

Dijkstra

单源最短路径问题(SSSP),我们通常使用 Dijkstra 算法。Dijkstra 算法,本质上是使用 BFS 和贪心解决单源图最短路径的问题。虽然但是,Dijkstra 算法不适用于有负边权的图。

所谓单源图,顾名思义,就是规定只有一个起点的图。

对于求解的图,假设任意两顶点之间距离为正无穷。然后开始加入边,更新当前源点与其他顶点的最短距离。将除起点外所有点加入未知集合,并将起点加入已知集合,直至确定该点到起点最短路径;依次更新起点到 i i i 的距离 dis[i],将未知集合 dis 中与起点距离最小的 x x x 加入已知集合;用 Floyd 的思想,若起点与 n n n 间距离大于起点到 x x x 距离加 x x x n n n 距离,更新 dis[n],更新与它相连的点;重复以上步骤直到终点进入已知集合即可。

我们可以用优先队列造小顶堆解决问题。

我们先把每一条边按照举例排序构造小顶堆,然后依次进行操作。

举个栗子,以下图为例:

Dijkstra 的基本思想,其实是先把每一个点的 dis 修改为无穷大,然后开始找最小 dis 点,然后枚举以该点为中转点到达的点比较路径长度试图修改。以 A A A 为源点,枚举当前点可以到达的点,第一次我们可以修改 B B B C C Cdis;此时 dis 最小的点为 C C C,所以下一次我们以 C C C 为中转点尝试转移,显然可以改变 dis[D]dis[E],由于以 C C C 为中转点到 B B B 的距离更优,所以 B B B 也可以被修改,以此类推。

时间复杂度为 O ( m log ⁡ n ) O(m\log n) O(mlogn) n n n 为顶点数, m m m 为边数。

struct node
{int u,dis;friend bool operator < (node a,node b){return a.dis>b.dis;//小顶堆!}
};priority_queue<node> q;void dij(int s)//s表示源点
{memset(diss,0x7f,sizeof(diss));diss[s]=0;q.push(node{s,0});while(!q.empty()){int u=q.top().id;q.pop();if(vis[u]) continue;vis[u]=1;for(int i=head[u];i;i=nxt[i]){if(diss[to[i]]>diss[u]+w[i]){diss[to[i]]=diss[u]+w[i];q.push(node{to[i],diss[to[i]]});}}}
}

练手板子题

代码如下:

#include <bits/stdc++.h>
using namespace std;const int maxn=2*1e5+5;
int head[maxn],nxt[maxn],to[maxn],w[maxn],cnt,dis[maxn],vis[maxn];void add(int x,int y,int z)
{to[++cnt]=y;w[cnt]=z;nxt[cnt]=head[x];head[x]=cnt;
}struct node
{int id,dis;friend bool operator < (node a,node b){return a.dis>b.dis;//小顶堆!}
};priority_queue<node> q;void dij(int s)
{memset(vis,0,sizeof(vis));memset(dis,0x3f,sizeof(dis));dis[s]=0;q.push(node{s,0});while(!q.empty()){int u=q.top().id;q.pop();if(vis[u]) continue;vis[u]=1;for(int i=head[u];i;i=nxt[i])if(dis[to[i]]>dis[u]+w[i])dis[to[i]]=dis[u]+w[i],q.push(node{to[i],dis[to[i]]});}
}int main()
{int n,m,s,u,v,w;cin>>n>>m>>s;for(int i=1;i<=m;i++) cin>>u>>v>>w,add(u,v,w);dij(s);for(int i=1;i<=n;i++) cout<<dis[i]<<' ';return 0;
}

SPFA

SPFA 其实是 Bellman-Ford 算法的队列优化算法的别称,常用于求含负边权的单源最短路径(参见 Johnson 算法)以及判负权环。

关于什么是负环,一条边权和为负数的回路就是负环。如果一个点被加入队列的次数大于等于总点数,那么不存在最短路,即一定存在负环。

最坏情况下,SPFA 算法的时间复杂度为 O ( V E ) O(VE) O(VE)(边数 × \times ×点数)。

SPFA 的流程为,每次从队列中取出队首点,尝试更新与这个点相连的点的 dis,若可以更新就将其入队。

代码如下:

void spfa()
{memset(dis,0x3f3f3f,sizeof(vis));dis[s]=0;z[top=1]=s;for(int j=1;j<=top;j++){int now=z[j];vis[now]=0;for(int head[now];i;i=nxt[i])if(dis[to[i]]>dis[now]+w[i]){dis[to[i]]=dis[now]+w[i];if(!vis[to[i]]) vis[to[i]]=1,z[++top]=to[i];}}
}

练手板子题

代码如下:

#include <bits/stdc++.h>
using namespace std;const int maxn=40005;
int nxt[maxn],to[maxn],head[maxn],val[maxn],dis[maxn],vis[maxn],rec[maxn],cnt,n,m;
queue<int> q;void add(int x,int y,int z)
{to[++cnt]=y;val[cnt]=z;nxt[cnt]=head[x];head[x]=cnt;
}bool spfa()
{memset(dis,127,sizeof(dis));memset(vis,0,sizeof(vis));memset(rec,0,sizeof(rec));while(!q.empty) q.pop();q.push(1);dis[1]=0,vis[1]=1,rec[1]++;while(!q.empty()){int u=q.front();q.pop();vis[u]=0;//队首已出队for(int i=head[u];i;i=nxt[i]){if(dis[to[i]]>dis[u]+val[i]){dis[to[i]]=dis[u]+val[i];//对于判断是否有负环,用数组rec记录点的入队次数,如果入队次数>n,就证明出现了负环导致没有最短路if(!vis[to[i]]) vis[to[i]]=true,rec[to[i]]++,q.push(to[i]);//能更新,压入队列if(rec[to[i]]>=n) return true;}}	}return false;
}int main()
{int T,u,v,w;cin>>T;while(T--){cin>>n>>m;memset(head,0,sizeof(head));cnt=0;for(int i=1;i<=m;i++){cin>>u>>v>>w;add(u,v,w);if(w>=0) add(v,u,w);}if(spfa()) cout<<"YES"<<endl;else cout<<"NO"<<endl;}return 0;
}

Johnson

Johnson 全源最短路算法,顾名思义就是一个名为 Johnson 的大神发明的一种求全源最短路的算法。可以解决图中任意起点的最短路问题。

首先考虑一种逆天的朴素做法,每次取一个点去跑 SPFA,时间复杂度 O ( m n 2 ) O(mn^2) O(mn2)(点数 × \times × 边数);或者干脆直接跑 O ( n 3 ) O(n^3) O(n3) 的 Floyd。显然这都是会炸掉的。

所以我们考虑一种很强的单源最短路径算法——Dijkstra。但是 Dijkstra 不能解决负边权,怎么办?

第一反应是把所有边的边权都加上一个数使其非负,但是显然可以被 Hack 掉:

对于上图,我们惊奇地发现,原先 1 1 1 2 2 2 的最短路是 1 → 5 → 3 → 2 1\rightarrow5\rightarrow3\rightarrow2 1532,结果变成正的之后最短路变成 1 → 4 → 2 1\rightarrow4\rightarrow2 142 了,寄。

Johnson 算法登场!它是一种可以替代上面逆天的负边权转正方法的算法。

我们新建一个虚拟节点编号为 0 0 0,这个点向其他所有点都连一条边权为 0 0 0 的边。然后跑一遍 SPFA,统计 0 0 0 到所有其他结点的最短路长度 h i h_i hi(为什么叫 h h h 是因为《算法导论》里这么叫)。如果存在一条边 u → v u\rightarrow v uv 边权为 w w w,那么将该边边权重新设置为 w + h u − h v w+h_u-h_v w+huhv

重新设置边权之后,我们就可以对于每一个节点跑一遍 Dijkstra 了。总时间复杂度 O ( n m log ⁡ m ) O(nm\log m) O(nmlogm)

如何证明 Johnson 算法的正确性?

首先我们证明经过这样一波操作之后最短路不会变。对于原最短路 s → p 1 → p 2 → ⋯ → p k → t s\rightarrow p_1\rightarrow p_2\rightarrow\cdots\rightarrow p_k\rightarrow t sp1p2pkt,用 Johnson 算法改变边权之后的长度可以表示为 ( w ( s , p 1 ) + h s − h p 1 ) + ( w ( p 1 , p 2 ) + h p 1 − h p 2 ) + ⋯ + ( w ( p k , t ) + h p k − h t ) (w(s,p_1)+h_s-h_{p_1})+(w(p_1,p_2)+h_{p_1}-h_{p_2})+\cdots+(w(p_k,t)+h_{p_k}-h_t) (w(s,p1)+hshp1)+(w(p1,p2)+hp1hp2)++(w(pk,t)+hpkht),化简之后为 w ( s , p 1 ) + w ( p 1 , p 2 ) + ⋯ + w ( p k , t ) + h s − h t w(s,p_1)+w(p_1,p_2)+\cdots+w(p_k,t)+h_s-h_t w(s,p1)+w(p1,p2)++w(pk,t)+hsht。如果原先的 s → t s\rightarrow t st 为最短路,那么更改之后其实就是加了个 h s − h t h_s-h_t hsht,因为这个 h s − h t h_s-h_t hsht 是定值,所以说原先的最短路和改变边权之后的最短路显然是一条路径因为原先要经过的点必须经过而无论中间取什么点都不可能改变加上的 h s − h t h_s-h_t hsht 的值,所以在新图上我们跑 Dijkstra 得到的最短路经过的点一定和原图相同。

接下来证明为什么边权处理之后一定非负。对于图中任意一条边 ( u , v ) (u,v) (u,v),一定满足 h v ≤ h u + w ( u , v ) h_v\leq h_u+w(u,v) hvhu+w(u,v),这是显然的,因为从 0 0 0 v v v 的最短路不可能超过从 0 0 0 u u u 的最短路加上 ( u , v ) (u,v) (u,v) 的边权,否则就会被松弛更新,其实这就是图论中的三角形不等式,最短路上的所有边都满足三角形不等式。于是乎用 Johnson 算法更改后的边权 w ′ ( u , v ) = w ( u , v ) + h u − h v w'(u,v)=w(u,v)+h_u-h_v w(u,v)=w(u,v)+huhv 一定是非负的,完结撒花!


练手板子题

代码如下:

#include <bits/stdc++.h>
using namespace std;
#define int long longconst int maxn=9005,maxx=1e9;//注意原有m条边+新建0节点n条边,数组小了会炸
int nxt[maxn],head[maxn],cnt,to[maxn],w[maxn],h[maxn],vis[3005],tim[3005],m,n,u,v,ww,dis[3005];void add(int x,int y,int z)
{to[++cnt]=y;w[cnt]=z;nxt[cnt]=head[x];head[x]=cnt;
}bool spfa()//SPFA判负环
{queue<int> q;memset(h,127/3,sizeof(h));h[0]=0,vis[0]=1;q.push(0);while(!q.empty()){int u=q.front();q.pop();vis[u]=0;for(int i=head[u];i;i=nxt[i]){int v=to[i];if(h[v]>h[u]+w[i]) {h[v]=h[u]+w[i];if(!vis[v]) {q.push(v),vis[v]=1,tim[v]++;if(tim[v]>n) return true;}}}}return false;
}struct node
{int id,dis;bool friend operator < (node a,node b){return a.dis>b.dis;}
};void dij(int s)
{priority_queue<node> q;memset(vis,0,sizeof(vis));for(int i=1;i<=n;i++) dis[i]=maxx;dis[s]=0;q.push(node{s,0});while(!q.empty()){int u=q.top().id;q.pop();if(vis[u]) continue;vis[u]=1;for(int i=head[u];i;i=nxt[i])if(dis[to[i]]>dis[u]+w[i])dis[to[i]]=dis[u]+w[i],q.push(node{to[i],dis[to[i]]});}
}signed main()
{cin>>n>>m;for(int i=1;i<=m;i++) cin>>u>>v>>ww,add(u,v,ww);for(int i=1;i<=n;i++) add(0,i,0);if(spfa()) cout<<-1,exit(0);for(int u=1;u<=n;u++) for(int i=head[u];i;i=nxt[i]) w[i]+=h[u]-h[to[i]];for(int i=1;i<=n;i++){dij(i);int ans=0;for(int j=1;j<=n;j++){if(dis[j]==maxx) ans+=j*maxx;else ans+=j*(dis[j]+h[j]-h[i]);}cout<<ans<<endl;}return 0;
}

总结

(下表中 m m m 为边数, n n n 为点数)

最短路算法FloydSPFADijkstraJohnson
最短路类型每对结点之间的最短路单源最短路单源最短路每对结点之间的最短路
适配的图任意图任意图非负权图任意图
能否检测负环不能
时间复杂度 O ( n 3 ) O(n^3) O(n3) O ( n m ) O(nm) O(nm) O ( m log ⁡ m ) O(m\log m) O(mlogm) O ( n m log ⁡ m ) O(nm\log m) O(nmlogm)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/166941.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法刷题-链表

算法刷题-链表 203. 移除链表元素 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xff1a;[1,2,3,4,5]…

asp.net社区医疗辅助诊断网站系统VS开发sqlserver数据库web结构c#编程

一、源码特点 asp.net社区医疗辅助诊断网站系统 是一套完善的web设计管理系统&#xff0c;系统采用mvc模式&#xff08;BLLDALENTITY&#xff09;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver200…

基于白鲸优化的BP神经网络(分类应用) - 附代码

基于白鲸优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于白鲸优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.白鲸优化BP神经网络3.1 BP神经网络参数设置3.2 白鲸算法应用 4.测试结果&#xff1a;5.M…

C语言指针

指针 文章目录 指针1.指针概念2.指针变量2.1 定义指针变量2.2 引用指针变量2.3 指针变量作为函数参数 3.通过指针引用数组3.1数组元素的指针3.2 在引用数组元素时指针的运算3.3通过指针引用数组元素3.4用数组名作函数参数3.5 通过指针引用多维数组 4.通过指针引用字符串4.1字符…

超详细 | 差分进化算法原理及其实现(Matlab/Python)

差分进化(Differential Evolution&#xff0c;DE)算法是由美国学者Storn和 Price在1995年为求解Chebyshev多项式拟合问题而提出的。算法主要通过基于差分形式的变异操作和基于概率选择的交叉操作进行优化搜索&#xff0c;虽然其操作名称和遗传算法相同&#xff0c;但实现方法有…

最新Tuxera NTFS2024破解版mac读写NTFS磁盘工具

Tuxera NTFS for Mac是一款Mac系统NTFS磁盘读写软件。在系统默认状态下&#xff0c;MacOSX只能实现对NTFS的读取功能&#xff0c;Tuxera NTFS可以帮助MacOS 系统的电脑顺利实现对NTFS分区的读/写功能。Tuxera NTFS 2024完美兼容最新版本的MacOS 11 Big Sur&#xff0c;在M1芯片…

Prometheus接入AlterManager配置邮件告警(基于K8S环境部署)

文章目录 一、配置AlterManager告警发送至邮箱二、Prometheus接入AlterManager配置三、部署PrometheusAlterManager(放到一个Pod中)四、测试告警 注意&#xff1a;请基于 PrometheusGrafana监控K8S集群(基于K8S环境部署)文章之上做本次实验。 一、配置AlterManager告警发送至邮…

EF执行迁移时提示provider: SSL Provider, error: 0 - 证书链是由不受信任的颁发机构颁发的

ef在执行时提示provider: SSL Provider, error: 0 - 证书链是由不受信任的颁发机构颁发的。 只需要在数据库链接字符串后增加EncryptTrue;TrustServerCertificateTrue;即可 再次执行

好用的办公软件有哪些

日常的工作难免和各种各样的软件打交道&#xff0c;除了传统的Office三件套&#xff0c;小编日常还在用着其他的办公软件&#xff0c;借此跟各位分享其中比较好用、堪称办公神器的8款软件&#xff01; 1.WPS office 2.office2007 3.EasyConnect 4.ToDesk 5.Photoshop 6.A…

​CUDA学习笔记(五)GPU架构

本篇博文转载于https://www.cnblogs.com/1024incn/tag/CUDA/&#xff0c;仅用于学习。 GPU架构 SM&#xff08;Streaming Multiprocessors&#xff09;是GPU架构中非常重要的部分&#xff0c;GPU硬件的并行性就是由SM决定的。 以Fermi架构为例&#xff0c;其包含以下主要组成…

Git 安装和基础命令、IDEA 基础操作

目录 总结命令&#xff1a;1、安装&#xff1a;1、安装2、配置环境变量&#xff1a; 2、Git操作&#xff1a;1、初始化&#xff1a;1、姓名邮箱&#xff1a;2、初始化仓库&#xff1a;3、工作区和暂存区分析 2、提交文件3、查看版本库状态4、安装小乌龟git不显示图标 5、查看提…

H3C SecParh堡垒机 get_detail_view.php 任意用户登录漏洞

与齐治堡垒机出现的漏洞不能说毫不相关&#xff0c;只能说一模一样 POC验证的url为&#xff1a; /audit/gui_detail_view.php?token1&id%5C&uid%2Cchr(97))%20or%201:%20print%20chr(121)%2bchr(101)%2bchr(115)%0d%0a%23&loginadmin成功获取admin权限 文笔生疏…

智慧公厕系列产品:为您提供更便捷、更卫生的厕所体验

智慧公厕系列产品致力于改善公共厕所的管理和使用体验&#xff0c;通过引入先进的科技和智能设备&#xff0c;提升厕所的安全、卫生、舒适性。这些产品涵盖了从厕位监测到环境调控&#xff0c;从安全防范到能耗监测的各个方面&#xff0c;为用户提供了一个更加方便、舒适、卫生…

【excel】列转行

列转行 工作中有一些数据是列表&#xff0c;现在需要转行 选表格内容&#xff1a;在excel表格中选中表格数据区域。点击复制&#xff1a;在选中表格区域处右击点击复制。点击选择性粘贴&#xff1a;在表格中鼠标右击点击选择性粘贴。勾选转置&#xff1a;在选择性粘勾选转置选…

LeetCode算法栈—验证图书取出顺序

验证图书取出顺序 目录 验证图书取出顺序 题解&#xff1a; 代码&#xff1a; 运行结果&#xff1a; 验证图书取出顺序 现在图书馆有一堆图书需要放入书架&#xff0c;并且图书馆的书架是一种特殊的数据结构&#xff0c;只能按照 一定 的顺序 放入 和 拿取 书籍。 给定一个…

vue3 element-plus 组件table表格 勾选框回显(初始化默认回显)完整静态代码

<template><el-table ref"multipleTableRef" :data"tableData" style"width: 100%"><el-table-column type"selection" width"55" /><el-table-column label"时间" width"120">…

mysql MVC jsp实现表分页

mysql是轻量级数据库 在三层架构中实现简单的分页 在数据库sql编程中需要编写sql语句 SELECT * FROM sys.student limit 5,5; limit x,y x是开始节点&#xff0c;y是开始节点后的需要显示的长度。 在jdbc编程中需要给出x和y 一般是页数*页码&#xff0c;显示的长度。 代…

服务端监控要怎么做?

目录 前言 一、Google的四类黄金指标 二、RED方法 三、USE方法 RED方法 vs USE方法 四、监控指标 WEB服务监控 MySQL数据库监控 QPS TPS 最大连接数 缓存监控 总结 前言 众所周知&#xff0c;业界各种大中型软件系统在生产运行时&#xff0c;总会有一些手段来…

linux性能分析(四)如何学习linux性能优化

一 如何学习linux性能优化 强调&#xff1a; 由于知识记忆曲线以及某些知识点不常用,所以一定要注重复习思考&#xff1a; 如何进行能力转义以及能力嫁接? --> 真正站在巨人的肩膀上性能调优的目的&#xff1a; 不影响系统稳定性的资源最大利用化补充&#xff1a; 性能…

【数据结构】顺序表实现通讯录

前言 在上一节中我们实现了顺序表&#xff0c;现在我们将使用顺序表完成通讯录的实现。&#xff08;注&#xff1a;本人水平有限&#xff0c;“小屎山”有些许bug&#xff0c;代码冗余且语无伦次&#xff0c;望谅解&#xff01;&#x1f605;&#xff09; 文章目录 一、数据结构…