最短路相关笔记

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.mzph.cn/news/115036.shtml

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

相关文章

如何让ChatGPT生成图片?

目录 一、那么如何解决让ChatGPT具有画图能力的问题呢&#xff1f; 二、那ChatGPT为什么能生成图片呢&#xff1f; 我们都知道ChatGPT只是个纯文本的AI模型&#xff0c;不具备画图能力。它可以生成文本&#xff0c;但如果让他生成图片就会显示如下的声明&#xff1a; 但通过本…

图像信号处理板设计原理图:2-基于6U VPX的双TMS320C6678+Xilinx FPGA K7 XC7K420T的图像信号处理板

综合图像处理硬件平台包括图像信号处理板2块&#xff0c;视频处理板1块&#xff0c;主控板1块&#xff0c;电源板1块&#xff0c;VPX背板1块。 一、板卡概述 图像信号处理板包括2片TI 多核DSP处理器-TMS320C6678&#xff0c;1片Xilinx FPGA XC7K420T-1FFG1156&#xff0c;1片X…

strcpy函数

文章目录 strcpy函数描述函数使用总结目标空间为什么必须可变&#xff1f;模拟实现 strcpy函数描述 重点&#xff1a;including the terminating null character (and stopping at that point).意为拷贝的值包括停止字符 传参时第一个参数为要拷贝参数&#xff0c;第二个参数为…

游戏盾如何有效防护DDoS

从进入计算机时代以来&#xff0c;DDoS攻击一直是网络世界中的一大威胁&#xff0c;让无数服务陷入瘫痪。这种攻击的原理非常简单&#xff1a;攻击者使用大量的僵尸主机或蠕虫病毒&#xff0c;向目标服务器发送海量请求&#xff0c;迅速耗尽服务器的资源&#xff0c;使其无法继…

某全球领先的芯片供应商:优化数据跨网交换流程,提高安全管控能力

1、客户介绍 某全球领先的芯片供应商&#xff0c;成立于2005年&#xff0c;总部设于北京&#xff0c;在国内上海、深圳、合肥等地及国外多个国家和地区均设有分支机构和办事处&#xff0c;致力于为客户提供更优质、便捷的服务。 2、建设背景 该公司基于网络安全管理的需求&am…

Datawhale学习笔记AI +新能源:电动汽车充电站充电量预测2

在飞浆平台上成功运行出pandas-profiling啦~ 首先一键安装 pip install ydata_profiling然后演示&#xff0c;可以生成一个网页对数据有一个比较好的理解 import numpy as np import pandas as pd from ydata_profiling import ProfileReporttrain_power pd.read_csv(/home/…

【论文阅读】以及部署BEVFusion: A Simple and Robust LiDAR-Camera Fusion Framework

BEVFusion: A Simple and Robust LiDAR-Camera Fusion Framework BEVFusion&#xff1a;一个简单而强大的LiDAR-相机融合框架 NeurIPS 2022 多模态传感器融合意味着信息互补、稳定&#xff0c;是自动驾驶感知的重要一环&#xff0c;本文注重工业落地&#xff0c;实际应用 融…

Flutter的Don‘t use ‘BuildContext‘s across async gaps警告解决方法

文章目录 问题有问题的源码 问题原因问题分析Context的含义BuildContext的作用特殊情况 解决方法 问题 Flutter开发中遇到Don’t use BuildContext’s across async gaps警告 有问题的源码 if (await databaseHelper.isDataExist(task.title)) {showDialog(context: context,…

想要精通算法和SQL的成长之路 - 找到最终的安全状态

想要精通算法和SQL的成长之路 - 找到最终的安全状态 前言一. 找到最终的安全状态1.1 初始化邻接图1.2 构建反向邻接图1.3 BFS遍历1.4 完整代码 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 找到最终的安全状态 原题链接 我们从题目中可以看出来&#xff1a; 出度为0的…

面向对象(基础)特征一:封装性(encapsulation)

文章目录 一、介绍&#xff08;1&#xff09;封装性&#xff08;2&#xff09;权限修饰符 二、案例&#xff08;1&#xff09;案例1 三、练习&#xff08;1&#xff09;练习1&#xff08;2&#xff09;练习2&#xff08;3&#xff09;练习3&#xff08;4&#xff09;练习4 面向…

我做不到受每个人喜欢

我做不到受每个人喜欢 我想描述一下昨天发生争吵后我个人的观点&#xff0c;希望能够重新呈现出一种积极的态度。 首先&#xff0c;让我简要梳理一下事件的经过&#xff0c;当天我像往常一样去另一个宿舍找人聊天&#xff0c;可能因为说话声音有点大&#xff0c;坐在我后面的那…

运维:mysql常用的服务器状态命令

目录 1、查询当前服务器运行的进程 2、查询最大链接数 3、查询当前链接数 4、展示当前正在执行的sql语句 5、查询当前MySQL当中记录的慢查询条数 6、展示Mysql服务器从启动到现在持续运行的时间 7、查询数据库存储占用情况 8、查询服务器启动以来的执行查询的总次数 9…

vue3 使用 elementUi: ./lib/theme-chalk/index.css is not exported from package

目录 1. 在 vue3 中使用 element-ui2. 如果启动报错&#xff1a;Module not found: Error: Package path ./lib/theme-chalk/index.css is not exported from package 1. 在 vue3 中使用 element-ui 在 vue3 中使用 element-ui&#xff0c;我们的流程一般是这样的&#xff1a;…

Spark--经典SQL50题

目录 连接数据库准备工作 1、查询"01"课程比"02"课程成绩高的学生的信息及课程分数 2、查询"01"课程比"02"课程成绩低的学生的信息及课程分数 3、查询平均成绩大于等于60分的同学的学生编号和学生姓名和平均成绩 4、查询平均成绩…

开源LLEMMA发布:超越未公开的顶尖模型,可直接应用于工具和定理证明

深度学习自然语言处理 原创作者&#xff1a;Winnie 今天向大家介绍一个新的开源大语言模型——LLEMMA&#xff0c;这是一个专为数学研究而设计的前沿语言模型。 LLEMMA解数学题的一个示例 LLEMMA的诞生源于在Proof-Pile-2数据集上对Code Llama模型的深度训练。这个数据集是一个…

TCP流套接字编程

文章目录 前言TCP 和 UDP 的特点对比TcpEchoServer 服务端实现1. 创建 ServerSocket 类实现通信双方建立连接2. 取出建立的连接实现双方通信3. 服务端业务逻辑实现关闭资源服务端整体代码 TcpEchoClient 客户端实现1. 创建出 Socket 对象来与服务端实现通信2. 实现客户端的主要…

OSI网络分层模型

OSI英文全文是Open System Interconnection Reference Model&#xff0c;翻译成中文就是开放式系统互联通信参考模型。 OSI模型分成了七层&#xff0c;部分层次与 TCP/IP 很像&#xff0c;从下到上分别是&#xff1a; 第一层&#xff1a;物理层&#xff0c;网络的物理形式&…

Mac安装nginx(Homebrew)

查看需要安装 nginx 的信息 brew info nginxDocroot 默认为 /usr/local/var/www 在 /opt/homebrew/etc/nginx/nginx.conf 配置文件中默认端口被配置为8080&#xff0c;从而使 nginx 运行时不需要加 sudo nginx将在 /opt/homebrew//etc/nginx/servers/ 目录中加载所有文件 …

10_集成学习方法:随机森林、Boosting

文章目录 1 集成学习&#xff08;Ensemble Learning)1.1 集成学习1.2 Why need Ensemble Learning?1.3 Bagging方法 2 随机森林(Random Forest)2.1 随机森林的优点2.2 随机森林算法案例2.3 随机森林的思考&#xff08;--->提升学习&#xff09; 3 随机森林&#xff08;RF&a…

解决pip安装包后但是Pycharm检测不到

首先要知道python找包的原理&#xff1a;原理 之后把一下代码打印一下&#xff1a; import sys print(sys.executable)# /usr/bin/python2 print(sys.path)# [/usr/lib/python2.7, /usr/lib/python2.7/dist-packages, /usr/local/lib/python2.7/dist-packages] print(sys.prefi…