首页 > 其他分享 >最短路径

最短路径

时间:2024-05-18 11:40:48浏览次数:17  
标签:结点 int 短路 路径 最短 MAXN dis

拓扑序

有这样一个问题:

我们给定一张 \(n\) 个点 \(m\) 条边的有向无环图(DAG),请求出从 \(1\) 号结点出发,到达任意结点的最短路径,保证 \(s\) 可以到达任意结点,\(n,m\leq 10^7\)。

我们以下面这张图为例。

image

如果我们想求 \(1 \rightarrow 4\) 的路径, 我们不难发现,找到 \(1 \rightarrow 4\) 的最短路径,就需要先找到 \(1 \rightarrow 2\) 或 \(1\rightarrow 3\) 的最短路径,\(1 \rightarrow 2\) 的路径只有一条,而 \(1\rightarrow 3\) 的最短路径则是 \(\min(1\rightarrow 3,1\rightarrow 2+2\rightarrow 3)\),而求出这里的最短路径,我们还需要需要先找到 \(1\rightarrow 1\) 的最短路径,\(1\rightarrow 1\) 的最短路径很明显是 \(0\),这样便可以求出 \(1\) 到任意结点的最短路径了。

我们发现,上图我们按照 \(1,2,3,4\) 的顺序遍历,这种顺序是拓扑序,我们只要按照拓扑序依次求解最短路,在拓扑排序过程中,删除出边指向的结点入度时,顺带更新出边指向结点的最短路,这样便可以求得答案了。

\(\texttt{code}\)

//dist[i] 1 到 i 的最短路径长度
//deg[i] i 的入度
void topu(int s){
	for(int i=1;i<=n;i++){
        dist[i]=-1e9;//边权可能为负数
    }
	q.push(s);
	dist[s]=0;
	while(!q.empty()){
		int k=q.front();
		q.pop();
		for(auto x:g[k]){
			dist[x.first]=min(dist[x.first],dist[k]+x.second);//更新最短路
			deg[x.first]--;
			if(deg[x.first]==0){//拓扑排序入队
                q.push(x.first);
             }
		}
    }
}

该解法的时间复杂度是 \(O(n+m)\)。

Floyd

上面我们讨论了从某一固定结点出发到达其他任何一个其他结点的最短路径,这种问题被称为单源最短路问题,有时候我们也会关心从任意结点,到任意结点的最短路径,这样的问题被称为全源最短路问题。

我们有一个简单的算法可以解决这样的问题。Floyd 算法的思路是,我们每次枚举一个中间结点,利用这个中间结点更新任意两点之间的最短路。 由于是求任意两点的最短路,所以我们经常采用邻接矩阵模型解决这个问题。

\(\texttt{code}\)

int g[MAXN][MAXN],dist[MAXN][MAXN];
void floyd(){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			dist[i][j]=g[i][j];
		}
	}
	for(int i=1;i<=n;i++){
		dist[i][i]=0;
	}
	for(int k=1;k<=n;k++){//枚举中间点
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){//枚举任意两点
				dist[i][j]=min(dist[i][j],dist[i][k] + dist[k][j]);//不经过 k 和经过 k 的路径求最小
			}
		}
	}
}

该解法的时间复杂度是 \(O(n^3)\)。

以最上面那张图为例,当我们枚举 \(k=1\) 时,我们考虑了所有途径 \(1\) 号结点,并且边数为 \(2\) 的最短路径。此时,最短路被更新为由一条路径或者两条边(必须经过 \(1\) 号结点)构成的最短路。

接着我们再枚举 \(k=2\),我们考虑了所有途径 \(2\) 号结点,边数为 \(2\) 或者 \(3\) 的最短路径。现在,我们的最短路被更新为由 \(1\)条,\(2\) 条或者 \(3\) 条边构成的,中间可能途径 \(1\) 或者 \(2\) 号结点的最短路。

我们不难发现,只需要按照上述规则归纳下去,经过 \(n\) 次枚举,我们得到的应该是经过 \(1\)条,\(2\) 条,......,\(n\) 条边构成的,中间可能途径 \(1,2,3,......n\) 的最短路。这不就是所有可能的情况对应的最短路径吗?

Dijkstra

Dijkstra 算法解决的是单源最短路的且边权全为正的问题,它的思想大致是:每次选择一个最短路径长度已经确定的结点,然后通过从这个结点出发的边,更新其余还未完全确定最短路径长度的结点。这样反复确定 \(n\) 次,就确定了所有结点的最短路。

因为边权为正数,所以当前未考虑的距离最小的结点一定不会被当前未考虑的距离较大的结点再额外加一条边给更新,所以从贪心的角度来说,当前未考虑的距离最小的 结点其最短路径长度已经确定下来了。

\(\texttt{code}\)

int g[MAXN][MAXN],dist[MAXN],vis[MAXN];//vis[i] 标记是否已选过
int dijkstra(int s){
	memset(dist,0x3f,sizeof(dist));
	dist[s]=0;
	for(int i=1;i<n;i++){
		int minn=-1,pos=2e9;
		for(int j=1;j<=n;j++){
			if(!vis[j]&&dist[j]<pos){//选择最短路径最小的未选过的结点
				minn=j;
				pos=dist[j];
			}
		}
		if(minn==-1){//图不连通
			break;
		}
		vis[minn]=1;
		for(int j=1;j<=n;j++){
			dist[j]=min(dist[j],dist[minn]+g[minn][j]);//从选中的已确定最短路的结点额外走一步
		}
	}
}

该解法的时间复杂度为 \(O(n^2)\),适用于稠密图。

当图为稀疏图时,我们可以使用堆对 Dijkstra 算法进行优化。

上面的解法中我们用循环找出最短路径长度最小的结点,我们可以采用一个堆,维护一个二元组,包含结点编号 \(u\),以及源点到 \(u\) 的最短路径长度。每次通过一条边更新 \(u\) 的最短路径的时候,我就把新的最短路径加入到堆里面。这样我就能保证堆顶的是当前最短路径长度最小的结点。

\(\texttt{code}\)

vector<pair<int,int>> g[MAXN];
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> pq;
int dis[MAXN],vis[MAXN];
int dijkstra(int s){
	memset(dis,0x3f,sizeof(dis));
	dis[s]=0;
	pq.push(make_pair(0,s));//把初始结点加入堆中
	while(!pq.empty()){
		pair<int,int> k=pq.top();
		pq.pop();
		if(vis[k.second]) continue; //每个结点只取出一次,如果取出多次,则不是最短路
		vis[k.second]=1;
		for(int i=0;i<g[k.second].size();i++){
			int to=g[k.second][i].first;
			int w=g[k.second][i].second;
			if(dis[to]>k.first + w){
				dis[to]=k.first + w;
				pq.push(make_pair(dis[to],to)); //把更新的最短路加入堆中
			}
		}
	}
	if(dis[n]==0x3f3f3f3f) return -1;
	else return dis[n];
}

该解法的时间复杂度是 \(O(m \log m)\),适用于稀疏图,但在稠密图时还不如普通的 Dijkstra。

Bellman-ford

Bellman-ford 算法适合在图存在负权边的时候求解单源最短路。这是因为,当我们运行 Dijkstra的时候,我们依赖一个性质,就是当前未确定最短路的结点之中,最短路长度最小的结点它的最短路实际已经确定下来了。反过来想,如果我们未确定该结点的最短路,那么到其他结点的最短路都比到该结点的最短路长,加上一条正权边之后,路径长度只会更长,所以最短路长度最小的结点,它的最短路已经确定下来了。

但是如果有负权边,那就不一定了,我们不能保证当前未确定最短路的结点之中最短路长度最小的结点的最短路已经确定下来了,因为我们可能先移动到一个最短路比它长的结点,然后通过经过一条负权边的方法使得答案变得更小。

而 Bellman-ford 算法的思想大致是如果图里存在一条最短路,那么最短路的长度不会超过 \(n-1\)。所以我枚举 \(n-1\) 轮,每一轮枚举所有的边,通过枚举出来的边, 把源点出发到其他结点的最短路逐渐构建出来。

\(\texttt{code}\)

struct edge{
	int from,to,w;
}edges[MAXM];//存边
int dist[MAXN];
void Bellman_Ford(int s){
    memset(dist,127/3,sizeof(dist));
    dist[s]=0;
    for(int i=1;i<n;i++){
    	for(int j=1;j<=m;j++){
    		if(dist[edges[j].from]+edges[j].w<dist[edges[j].to]){
  			    dist[edges[j].to] = dist[edges[j].from] + edges[j].w;
    		}
        }
    }
}

该解法的时间复杂度是 \(O(nm)\),但在稠密图是会退化到 $O(n^3) $,所以在只有正权边的情况下,不如 Dijkstra。

使用队列优化 Bellman-ford(SPFA)

\(\texttt{code}\)

vector<pair<int,int>> g[MAXN]; //邻接表模型
queue<int> q;//采用队列优化,只保存结点编号,不保存路径长度
int dis[MAXN],vis[MAXN];
int PushTime[MAXN]; //记录进队此时,如果这个次数达到n次,说明有负环
int n,m;
void QBellman_Ford(int s){
	memset(dis,0x3f,sizeof(dis));
	dis[s]=0;
	q.push(s);//把初始结点加入队列中
	PushTime[s]++;
	while(!q.empty()){
		int k=q.front();
		q.pop();
		vis[k]=0;//标记它又可以放回队列了
		for(int i=0;i<g[k].size();i++){
			int to = g[k][i].first;
			int w = g[k][i].second;
			if(dis[to] > dis[k] + w){
				dis[to] = dis[k] + w;
				if(!vis[to]){
					PushTime[to]++;
					q.push(to); //把更新的最短路加入队列中
					vis[to] = 1;
				}
			}
		}
	}
	if(dis[n]==0x3f3f3f3f)cout<<"impossible";
	else cout<<dis[n];
}

注意,该解法最坏情况下时间复杂度还是 \(O(nm)\)。

标签:结点,int,短路,路径,最短,MAXN,dis
From: https://www.cnblogs.com/shimingxin1007/p/18199172

相关文章

  • restcontroller中使用delete请求发送带参路径报错
    报错内容:Resolved[org.springframework.web.HttpRequestMethodNotSupportedException:Requestmethod'DELETE'notsupported]具体情景:postman中发送的路径: 每次用这个路径发送delete请求都报错,但是用这个路径发送get请求做查询却没问题,真的让我苦恼好久 解决:因为HttpC......
  • PKUSC 2024 最短路径
    本文首发于QOJhttps://qoj.ac/blog/skip2004/blog/866大家好,我是钱哥,我来写一下PKUSC2024最短路径的题解。没有做过这个题的同学可以先自行做一做。我们下面来讲解一下如何一步步解决这个题目。subtask4首先,我们来解决第一个具有挑战性的子任务:\(m\leq2.5n\),这个点具......
  • 修改VS中的NuGet包下载路径
    一、修改下载的NuGet包存储位置:1、文件路径:C:\Users\{登录用户名}\AppData\Roaming\NuGet2、打开修改“NuGet.Config”,文件内容原始文件内容为:xmlversion="1.0"encoding="utf-8"?<configuration><packageSources><addkey="nuget.org"......
  • 解决Python执行命令时路径空格引发的困扰
    哈喽,大家好,我是木头左!在编程实践中,常常需要通过Python执行系统命令,这包括调用外部程序、脚本或是其他命令行工具。然而,一个看似简单却常被忽视的细节——文件路径中的空格,可能会导致程序意外崩溃或行为异常。本篇文章将深入探讨当路径中包含空格时,如何在Python中正确执行命令,以......
  • 上百页html生成pdf解决方案(bookjs-easy)简洁完整版(包含接收服务端返回路径参数)
    依靠1:客户端插件 bookjs-easy(点击直接跳转官网)2:服务端插件screenshot-api-server实测105页的pdf,生成耗时40s左右,文件大小16MB项目需求:生成一个上百页的pdf,这个pdf包含表格、折线图、图片等,且横竖幅页面交叉 bookjs-easy官网的文档对于第一次看的人来说并不友好(建议第......
  • 【CodeChef】Prison Escape(最短路)
    题目大意:给出一个01矩阵,求每个0移动(每次可以向有公共边的格子移动一步)到矩阵边界至少要经过多少个1。考虑建最短路模型,将矩阵中的每个位置拆分为入点和出点,矩阵外部设为一个点。枚举矩阵中的每个位置:如果这个位置在矩阵边界,矩阵外部向这个位置的入点连一条长度为0的边。......
  • 使用joinjs绘制流程图(九)-实战-绘制流程图+节点路径自定义升级版
    效果图自定义路径设计本示例假设当前节点和其他节点不在同一纵坐标的情况下代码<template><divclass="app"><divref="myholder"id="paper"></div></div></template><script>import*asjointfrom'@joint/......
  • P10447 最短 Hamilton 路径 题解
    P10447最短Hamilton路径题解题目传送门题意:给定一张有向带权图(\(n\le20\))求点\(0\)到点\(n-1\)的最短哈密顿路径。这是一道状压DP模板题。在状态压缩DP中,我们将一个集合压成一个二进制数。设\(f_{u,i}\)为已经走了集合\(u\)中的节点,且现在在点\(i\)的最短......
  • P2765 魔术球问题(最小路径点覆盖)
    link这个题目很不同,它给出的是柱子的数量,要反推球的数量。可以这样认为,给出边数,求上面的点数。每次只能在某根柱子的最上面放球->点的连接方式是一串串的,易发现图是个DAG;然后好像没什么可推的性质了。题目没给出点数,那肯定要去不断试不同的点数n,每次进行判定是否符合......
  • 64 - Minimum Path Sum 最小路径和
    64-MinimumPathSum最小路径和问题描述Givenamxngridfilledwithnon-negativenumbers,findapathfromtoplefttobottomright,whichminimizesthesumofallnumbersalongitspath.Note:Youcanonlymoveeitherdownorrightatanypointinti......