最短路径

news/2024/11/17 20:39:23/文章来源:https://www.cnblogs.com/shimingxin1007/p/18199172

拓扑序

有这样一个问题:

我们给定一张 \(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)\)

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

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

相关文章

强大且免费的抠图网站(强烈安利!)

今天想把下载下来的图片进行抠图上传,找了好久没找到好用的,不是功能单一就是收费( 囊中羞涩 ),猛然间,发现一个神奇的网站,名叫 易可图!!易可图 是一款全方位的免费图片编辑网站,包含抠图、裁剪、拼接、压缩等常用功能,易可图抠图功能只需用户上传照片,就能一…

平行关系转化思维导图

线线、线面、面面平行关系转化思维导图前言 使用方法:如果想得到更好的显示效果,可以点击全屏按钮,已经实现电脑端、手机端的适配,效果很好;电视端没有实现适配,Ipad端的适配没有测试; 思维结构图全屏

adb

Android调试桥接器,简称adb(Android debug bridge)列出所有已连接的设备命令:adb devices运行指定的设备的shell命令:adb -s serialNumber shell从本地主机拷贝文件到远程主机命令:adb -s serialNumber push C:\Users\coolkit\Desktop\新建文件夹\test.txt /data从远程主…

[Java基础复习]注解

一、什么是注解注解是JDK1.5才引入的,Java增加了对元数据(描述数据的数据)的支持,也就是注解。 注解可以标注在类,成员变量,方法、形参上等。 注解可以做到在不改变代码逻辑的前提下在代码中嵌入补充信息。 这些信息被保存在注解的“name=value”键值对中。 框架=反射+注解…

基于Python的性能分析

1、什么是性能分析 字面意思就是对程序的性能,从用户角度出发就是运行的速度,占用的内存。 通过对以上情况的分析,来决定程序的哪部份能被优化。提高程序的速度以及内存的使用效率。 首先我们要弄清楚造成时间方面性能低的原因有哪些沉重的I/O操作,比如读取分析大文件,长时…

基于Azure DevOps 的 CICD 项目部署(.Net Core)

基于Azure DevOps 的 CICD 项目部署(.Net Core)使用微软的 来进行CICD 链接:https://dev.azure.com创建新项目创建项目名称选择仓库地址选择空模板创建代理池按照以下步骤把代理部署到服务器上 ![连接你的服务器创建新的文件夹 mkdir myange cd myagent可通过链接下载文件…

LeetCode 1353. Maximum Number of Events That Can Be Attended

原题链接在这里:https://leetcode.com/problems/maximum-number-of-events-that-can-be-attended/description/ 题目: You are given an array of events where events[i] = [startDayi, endDayi]. Every event i starts at startDayi and ends at endDayi. You can attend a…

PaliGemma 正式发布 — Google 最新发布的前沿开放视觉语言模型

PaliGemma 是 Google 推出的新一代视觉语言模型家族,能够接收图像与文本输入并生成文本输出。 Google 团队已推出三种类型的模型:预训练(PT)模型、混合模型和微调(FT)模型,这些模型分辨率各异,提供多种精度以便使用。 所有模型均在 Hugging Face Hub 的模型库中发布,配…

elementUI 使用 el-select 的远程搜索功能,导致数据无法回显怎么解决?

问题:解决方法: 在数据初始化的时候将获取到的数据做进一步的处理,进行本地select组件的一个添加 const labels = [];const values = [];res.data.rows.forEach((ele) => {labels.push(ele.buildName);values.push(ele.buildCode);});this.$refs.select.cachedOptions = …

基于uniapp+vue3自定义增强版table表格组件「兼容H5+小程序+App端」

vue3+uniapp多端自定义table组件|uniapp加强版综合表格组件 uv3-table:一款基于uniapp+vue3跨端自定义手机端增强版表格组件。支持固定表头/列、边框、斑马纹、单选/多选,自定义表头/表体插槽、左右固定列阴影高亮显示。支持编译兼容H5+小程序端+App端。 如下图:H5+小程序+A…

扩散模型的多元化应用:药物发现、文本生成、时间序列预测等

AlphaFold3 是 DeepMind 开发的一款蛋白质结构预测软件,它在AlphaFold2的基础上进行了改进。其中最主要的一个改进就是引入了扩散模型,这使得扩散模型不仅仅可以应用于文生图,其实扩散模型早已经在各个领域中都又所发展,今天我们就来研究一下扩散模型的多元化应用。https:/…

FFmpeg开发笔记(二十一)Windows环境给FFmpeg集成AVS3解码器

​AVS3是中国AVS工作组制定的第三代音视频编解码技术标准,也是全球首个已推出的面向8K及5G产业应用的视频编码标准。AVS工作组于2019年3月9日完成第三代AVS视频标准(AVS3)基准档次的制订工作,参考软件的测试表明,AVS3基准档次的性能比上一代标准AVS2和HEVC提升了约30%。li…