最短路相关笔记

news2024/11/19 18:40:08

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 long

const 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.coloradmin.cn/o/1116983.html

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

相关文章

Qt扫盲-QBrush理论使用总结

Q 理论使用总结 一、概述1. 填充模式2. 笔刷颜色3. 纹理 二、 Qt::GlobalColor 一、概述 QBrush类定义了由 QPainter 绘制的形状的填充模式。画笔有样式、颜色、渐变和纹理。 brush style() 使用Qt::BrushStyle 枚举定义填充模式。默认的笔刷样式是 Qt::NoBrush(取决于你如何…

全光谱护眼灯有哪些?2023全光谱护眼台灯推荐

随着电子设备的不断普及&#xff0c;手机、平板电脑、显示器、电视机等几乎是家家户户的必备品&#xff0c;也正因为眼睛有那么多时间、那么多机会去盯着屏幕&#xff0c;所以如今近视低龄化现象也越来越严重了。随着科技的不断发展&#xff0c;台灯的发展也越来越多样化&#…

在模拟冷藏牛肉加工条件下,冷和酸对荧光假单胞菌和单核细胞增生李斯特菌双菌种生物膜的综合影响

1.1 Title&#xff1a;Combined effects of cold and acid on dual-species biofilms of Pseudomonas fluorescens and Listeria monocytogenes under simulated chilled beef processing conditions 1.2 分区/影响因子&#xff1a;Q1/5.3 1.3 作者&#xff1a;Zhou Guanghui…

黎曼几何与切空间之间的投影

公式&#xff1a; 从黎曼空间投影到切空间&#xff0c;其中P为黎曼均值&#xff0c;也是切空间的参考中心点&#xff0c;Pi是要投影到切空间的点。 从切空间投影回来&#xff0c;其中Si为切空间中的向量。 function Tcov CovToTan(cov,Mcov)Cm12 Mcov^(-1/2);X_new logm(Cm…

Android之自定义View之底部选择弹窗

一、底部选择弹窗 效果&#xff1a; 1、自定义类 可自定义item数量的底部弹窗 &#xff08;1&#xff09;CustomBottomPop自定义类 package com.custom.jfrb.ui.first.customView; //自己的包位置import android.content.Context; import android.view.LayoutInflater; i…

聚观早报 | 遥感AI大模型发布;拼多多启动11.11大促

【聚观365】10月21日消息 遥感AI大模型发布 拼多多启动11.11大促 OPPO Find N3开启预售 魅族20 Classic开启预定 埃安昊铂HT开启预售 遥感AI大模型发布 阿里达摩院发布业内首个遥感AI大模型&#xff0c;一个模型即可识别农田、农作物、建筑等地表万物&#xff0c;让AI进一…

【2023淘宝双十一活动什么时间开始?天猫双十一2023具体时间安排

2023双十一活动什么时间开始&#xff1f;让我们先来了解一下双十一的优惠活动以及玩法吧。请收藏这份2023年淘宝天猫双十一玩法优惠攻略&#xff0c;让你轻松购得心仪的商品&#xff01; 红包派送 活动期间&#xff0c;每天都可以领取超级红包&#xff01;请注意&#xff0c…

【JavaEE重点知识归纳】第8节:面向对象程序三大特性:封装、继承、多态

目录 一&#xff1a;封装 1.封装的概念 2.访问限定符 3.封装扩展包 二&#xff1a;继承 1.概念 2.语法 3.父类成员的访问 4.super关键字 5.子类构造方法 6.super和this 7.继承方式 8.final关键字 9.继承和组合 三&#xff1a;多态 1.概念 2.实现条件 3.重写 …

【supervisor】 问题处理 unix:///var/run/supervisor/supervisor.sock no such file

问题描述 最近笔者在配置 supervisor&#xff0c;希望用 supervisor 进行进程服务管理的时候遇到了一些问题&#xff0c;具体是&#xff1a; 运行 supervisorctl status 的时候遇到了 unix:///var/run/supervisor/supervisor.sock no such file 的问题。在运行 sudo service …

MySQL日志 错误日志 二进制日志 查询日志 慢查询日志

一、错误日志 错误日志是MySQL中最重要的日志之一,它记录了当mysqld启动和停止时,以及服务器在运行过程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时,建议首先查看此日志。 该日志是默认开启的&#xff0c;默认存放目录/var/log/,默认的日志文件名…

了解多媒体展厅弧幕投影系统收费构成,轻松制定预算

随着数字多媒体技术在内容展示行业中的广泛应用&#xff0c;基于投影、LED等技术手段的多媒体互动装置呈现多样化发展趋势&#xff0c;越来越多的新颖模式出现在大众眼前&#xff0c;其中就包括了备受关注的弧幕投影系统&#xff0c;作为投影技术显示形式的一种&#xff0c;它打…

抖音招聘直播报白有成本低和招聘效果精准的优势

抖音的短视频流量能够让岗位信息覆盖更广泛的人群&#xff0c;增加招聘信息的曝光度。通过抖音的短视频流量红利和精准推送&#xff0c;能够提高岗位信息的曝光度和求职者的留存率。如果你想做招聘报白却不知道怎么处理&#xff0c;可以咨询我。 创意创新:抖音招聘注重创意和创…

遍历完全二叉树节点

完全二叉树的节点个数 递归普通二叉树遍历解法 我们先来普及一下完全二叉树的概念&#xff1a;在完全二叉树中&#xff0c;除了最底层节点可能没填满外&#xff0c;其余每层节点数都达到最大值&#xff0c;并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层…

C语言之排序

1.冒泡排序 冒泡排序就不多说了&#xff0c;只需要两层循环嵌套&#xff0c;两两比较确定相对正确的顺序即可。 2.插入排序 插入排序的思想就是每一次向后寻找一个再将其与前面有序的部分进行对比&#xff0c;寻找合适位置插入。 这里关键要避免让前移超出目前读取的数字&…

Java版ORM最初雏形

经过一个晚上的加班&#xff0c;终于把ORM初步结构工程搭好了。工程依赖有点难用&#xff0c;编辑器提示比VS差很多。 首先LIS.Core创建一个最初的容器雏形&#xff0c;先能反射得到对象给ORM获得数据库驱动 然后ORM创建数据库驱动差异接口&#xff0c;不同数据库实现接口后配…

MATLAB中 tf2zpk函数用法

目录 语法 说明 示例 IIR滤波器的极点、零点和增益 tf2zpk函数的功能是将传递函数滤波器参数转换为零极点增益形式。 语法 [z,p,k] tf2zpk(b,a) 说明 [z, p, k] tf2zpk(b, a) 从传递函数参数 b 和 a 中找到零点矩阵 z&#xff0c;极点向量 p&#xff0c;以及相关的增益…

岗亭一键报警器设计

岗亭一键报警器设计 岗亭一键报警器设计人性化&#xff0c;拥有多种功能&#xff0c;例如&#xff1a; 紧急报警&#xff1a;遇到紧急情况时&#xff0c;只需按下报警器按钮&#xff0c;即可迅速向监控中心发送报警信号&#xff0c;以便相关人员及时采取应对措施。远程监控&am…

NLP:从头开始的文本矢量化方法

一、说明 NLP 项目使用文本&#xff0c;但机器学习算法不能使用文本&#xff0c;除非将其转换为数字表示。这种表示通常称为向量&#xff0c;它可以应用于文本的任何合理单位&#xff1a;单个标记、n-gram、句子、段落&#xff0c;甚至整个文档。 在整个语料库的统计 NLP 中&am…

EPLAN_007#3D图形的导入、编辑和定义

一定要打开对象捕捉&#xff0c;否则会严重偏移&#xff01;&#xff01;&#xff01; 一、导入3D模型&#xff0c;合并模型 1、新建一个宏项目 2、导入&#xff08;3D图形&#xff09; 可以对目标进行旋转查看 3、合并图形&#xff08;不建议合并&#xff09; 框选目标 点合并…

用python写一个贪吃蛇的程序能运行能用键盘控制

用python写一个贪吃蛇的程序能运行能用键盘控制 1.源码2.运行效果 1.源码 开发库使用&#xff1a;pygame random 直接在终端运行&#xff1a;pip install pygame pycharm安装库&#xff1a;文件-设置-项目-Python 解释器 import pygame import random# 初始化pygame pygame…