初识最短路径

一.最短路径的介绍

最短路径是图论和网络分析中一个重要的概念,它指的是在一个图或网络中连接两个节点或顶点的路径中,具有最小权重总和的路径。这个权重可以表示为路径上边或弧的长度、耗费、时间等,具体取决于问题的背景和应用场景。

如果你有学过最小生成树,你会发现,二者是有部分相同点的,都是在找权重和的最小值。

但是最小生成树找的是沟通所有节点的最小权重,而最短路径是找的是一个节点到另一个节点的最小权重。

因为从一个节点到另一个节点往往有多种路径,路径的长短也不同,我们的目的就是为了找到一个最短的路径,来达到省时省力的目的。

比如我们从A节点到E节点,分别有三条路径。

上路径:2(A->B)+4(B->E)=6

中路径:10(A->E)

下路径:4(A->C)+2(C->D)+6(D->E)=12

那么我们怎么使用代码找出最短路径呢?

用于解决最短路径可以使用Dijkstra 算法、SPFA算法、Floyd 算法等。

下面我们对这几种算法来介绍一下。

二.算法介绍

这里先给出一个例题,使用下面介绍成所有算法解决。

例题

输入数据:

4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4

1.Dijkstra 算法

Dijkstra 算法基于贪心策略,通过逐步找到从起点到所有其他节点的最短路径来工作。它适用于权重为非负的有向图和无向图。

算法的实现有以下几个步骤:

1.distances:距离数组,用于存储从起点到每个节点的最短距离估计值。初始时,将起点到自身的距离设置为0,其他节点的距离设置为无穷大。

2.visited:标记数组,用于标记节点是否已经被访问过。初始时,将所有节点标记为未访问。

3.previous:前驱数组,用于记录到达每个节点的最短路径中,当前节点的前一个节点是哪个。

4.从起点开始,将起点标记为当前节点,更新起点到相邻节点的距离,并将这些相邻节点加入候选集。

5.从候选集中选择距离最短的节点作为当前节点,并将其标记为已访问。

6.对于当前节点的每个相邻节点,如果经过当前节点到达该相邻节点的路径比起始点直接到达该节点的路径更短,则更新距离数组和前驱数组。

7.重复步骤3和步骤4,直到所有节点都被访问过或候选集为空。

8.最终得到起点到每个节点的最短路径长度和最短路径。

下面是实现例题的完整代码(含注释):

#include<bits/stdc++.h>
#define M 500010
#define inf 1234567890
using namespace std;
struct edge{int u,v,w,next;
}e[M];
struct node{int w,now;bool operator<(const node &k)const{return w>k.w;//堆优化,小的元素放在堆顶,大根堆 }
};
int head[M],re=0,n,m,s,v[M],dis[M];
priority_queue<node>q;
void add(int u,int v,int w)//链式前向星存图 
{e[++re].u=u;e[re].v=v;e[re].w=w;e[re].next=head[u];head[u]=re;
}
void dijkstra()
{for(int i=1;i<=n;i++) dis[i]=inf;//将点设置为无穷大 dis[s]=0;//起点设置为0 node p;p.w=0,p.now=s;q.push(p);while(!q.empty()){node k=q.top();q.pop();int u=k.now;if(v[u]) continue;//如果遍历过,直接跳过循环 v[u]=1;for(int i=head[u];i;i=e[i].next){//下一个点 int v=e[i].v;if(dis[v]>dis[u]+e[i].w)//更新最小权重 {dis[v]=dis[u]+e[i].w;q.push((node){dis[v],v});}		}}
}
int main()
{cin>>n>>m>>s;for(int i=0;i<m;i++){int u,v,w;cin>>u>>v>>w;//建边 add(u,v,w);}dijkstra();for(int i=1;i<=n;i++){cout<<dis[i]<<" ";}return 0;
}

我们来看运行结果:

也是成功AC。

2.SPFA算法

SPFA算法是一种用于解决单源最短路径问题的算法,是 Bellman-Ford 算法的一种优化版本。它通过队列实现松弛操作,以减少不必要的松弛次数,从而提高了算法的效率。

算法实现步骤:
1.distances:距离数组,用于存储从起点到每个节点的最短距离估计值。初始时,将起点到自身的距离设置为0,其他节点的距离设置为无穷大。

2.queue:队列,用于存储待处理的节点。初始时,将起点加入队列。

3.in_queue:标记数组,用于标记节点是否已经在队列中。初始时,将起点标记为在队列中。

4.从队列中取出一个节点作为当前节点,遍历该节点的所有相邻节点。

5.对于每个相邻节点,如果经过当前节点到达该相邻节点的路径比起始点直接到达该节点的路径更短,则更新距离数组,并将该相邻节点加入队列。

6.重复直到队列为空。

下面是实现例题的完整代码:

#include<bits/stdc++.h>
#define M 500010
#define inf 1234567890
using namespace std;
struct edge{int u,v,w,next;
}e[M];
int head[M],re=0,n,m,s,v[M],dis[M];
void add(int u,int v,int w)//链式前向星存图 
{e[++re].u=u;e[re].v=v;e[re].w=w;e[re].next=head[u];head[u]=re;
}
queue<int>q;//队列优化 
void SPFA()
{for(int i=1;i<=n;i++) dis[i]=inf;//将点设置为无穷大 dis[s]=0;//起点设置为0 q.push(s);while(!q.empty()){//直到所有节点全部入队,不会再有新的节点入队,慢慢出队至停止循环 int k=q.front();q.pop();v[k]=0;for(int i=head[k];i;i=e[i].next){//下一个点 int p=e[i].v;if(dis[p]>dis[k]+e[i].w)//更新最小权重 {dis[p]=dis[k]+e[i].w;if(!v[p])//如果不在队列里面,则入队 {v[p]=1;q.push(p);}}		}}
}
int main()
{cin>>n>>m>>s;for(int i=0;i<m;i++){int u,v,w;cin>>u>>v>>w;//建边 add(u,v,w);}SPFA();for(int i=1;i<=n;i++){cout<<dis[i]<<" ";}return 0;
}

下面是运行结果:

虽然运行结果正确,但是SPFA算法容易被卡数据,导致时间超限。

3.Floyd 算法

Floyd算法的时间复杂度为O(N^3),其中N是节点的数量。因此,对于大型图,Floyd算法可能会变得相对缓慢。然而,与其他单源最短路径算法不同,Floyd算法可以同时计算任意两个节点之间的最短路径,这在某些情况下可能是更方便的。

Floyd算法的核心是下面这个方程式,来自动态规划。

dp[j][k]=min(dp[j][k],dp[j][i]+dp[i][k]);

算法实现步骤:

  1. 初始化距离矩阵:将矩阵D初始化为图的邻接矩阵,如果两个节点之间有直接的边,则距离为边的权重;否则,距离为无穷大。同时,将对角线上的元素(即节点到自身的距离)初始化为0。

  2. 三重循环:对于每一对节点(i,j)作为可能的中间节点k,检查是否存在路径从节点i到节点j通过节点k的路径比直接从i到j的路径更短。如果是,则更新路径长度。

  3. 返回最终的距离矩阵D,其中D[i][j]表示从节点i到节点j的最短路径长度。

Floyd算法是这三种算法中最简单的,核心代码只有一点。

for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){if(i==j||dp[j][i]==inf) continue;for(int k=1;k<=n;k++){dp[j][k]=min(dp[j][k],dp[j][i]+dp[i][k]);} }}

下面是完整代码:

#include<bits/stdc++.h>
using namespace std;
#define M 10010
#define inf 1234567890
int n,m,s,dp[M][M];
void floyd()
{for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){if(i==j||dp[j][i]==inf) continue;//找存在边 for(int k=1;k<=n;k++){dp[j][k]=min(dp[j][k],dp[j][i]+dp[i][k]);//更新最小权重 } }}
}
int main()
{cin>>n>>m>>s;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)dp[i][j]=inf;//初始化矩阵 for(int i=1;i<=m;i++){int u,v,w;cin>>u>>v>>w;dp[u][v]=min(dp[u][v],w);//防止重边 }dp[s][s]=0;floyd();for(int i=1;i<=n;i++){cout<<dp[s][i]<<" ";}return 0;
}

下面是运行结果:

结果虽然正确,但是对于这题来说,运行速度太慢了。

所以在做题时,需要按照题目要求来选择合适算法。

三.总结

  1. Dijkstra算法:

    • 简介: Dijkstra算法是解决单源最短路径问题的一种贪心算法。它从起点开始,逐步找到从起点到所有其他节点的最短路径。
    • 运行速度: 在最坏情况下,Dijkstra算法的时间复杂度为O((V+E)logV),其中V是节点数,E是边数。在使用最小堆的实现中,它通常具有较好的性能。
  2. SPFA算法:

    • 简介: SPFA算法是Bellman-Ford算法的一种优化版本,通过使用队列来避免不必要的重复松弛操作,提高了效率。
    • 运行速度: SPFA算法的平均时间复杂度通常被认为是O(kE),其中k是一个常数。在实际应用中,它的性能通常比Bellman-Ford算法好,但对于存在负权回路的图,SPFA算法可能陷入死循环。
  3. Floyd算法:

    • 简介: Floyd算法是一种动态规划算法,用于解决图中所有节点对之间的最短路径问题。它通过迭代更新每一对节点之间的最短路径长度。
    • 运行速度: Floyd算法的时间复杂度为O(N^3),其中N是节点的数量。在大型图上可能变得相对缓慢,但与其他单源最短路径算法不同,Floyd算法可以同时计算任意两个节点之间的最短路径。

速度比较:

  • Dijkstra算法通常在稠密图上表现良好,特别是当使用最小堆等数据结构进行优化时。
  • Floyd算法的运行速度可能在大型图上较慢,但由于同时计算所有节点对之间的最短路径,它在某些情况下可能更方便。

总体而言,选择算法通常取决于图的规模、稀密度、边权重分布以及是否需要同时计算所有节点对之间的最短路径。


本篇完~

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

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

相关文章

PHP服务商微信支付分支付(需确认模式)

//查询支付分是否支付 public function serviceorderServiceorder($out_order_no) {$setting [];$service_id $setting[service_id];$sub_mchid $setting[mchid];$ps "/v3/payscore/partner/serviceorder?service_id${service_id}&sub_mchid${sub_mchid}&out…

【自然语言处理】实验3,文本情感分析

清华大学驭风计划课程链接 学堂在线 - 精品在线课程学习平台 (xuetangx.com) 代码和报告均为本人自己实现&#xff08;实验满分&#xff09;&#xff0c;只展示主要任务实验结果&#xff0c;如果需要详细的实验报告或者代码可以私聊博主 有任何疑问或者问题&#xff0c;也欢…

Java 学习和实践笔记(12)

这个就比较有意思了&#xff01;所有的事情&#xff0c;拆分完之后&#xff0c;都有且只有这三种状态流程&#xff01; //TIP To <b>Run</b> code, press <shortcut actionId"Run"/> or // click the <icon src"AllIcons.Actions.Execute&…

LGAMEFI基于BPL公链开发的第一生态:开启RWA游戏娱乐与DeFi融合的新纪元

在去中心化金融&#xff08;DeFi&#xff09;与游戏娱乐的结合趋势中&#xff0c;BPL公链上的LGAMEFI项目代表了前沿的技术革新和市场领导。这种将web2上成熟页游进行RWA链改&#xff0c;不仅仅是将游戏热门领域融合&#xff0c;更是在寻找一种全新的参与者经验&#xff0c;将玩…

archLinux安装记录

archLinux安装记录 基于wsl的arch 启用wsl 首先&#xff0c;按Win S搜索启用或关闭Windows功能&#xff08;Turn Windows features on or off&#xff09;,打开虚拟机平台和WSL&#xff0c;并重启Windows。 重启后&#xff0c;进入Windows设置&#xff0c;检查更新。 更新完…

『运维备忘录』之 Sed 命令详解

运维人员不仅要熟悉操作系统、服务器、网络等只是&#xff0c;甚至对于开发相关的也要有所了解。很多运维工作者可能一时半会记不住那么多命令、代码、方法、原理或者用法等等。这里我将结合自身工作&#xff0c;持续给大家更新运维工作所需要接触到的知识点&#xff0c;希望大…

css篇---移动端适配的方案有哪几种

移动端适配 移动端适配是指同一个页面可以在不同的移动端设备上都有合理的布局。主流实现的方案有 响应式布局通过rem或者vw,vh 等实现不同设备有相同的比例而实现适配 首先需要了解viewport 【视口】 视口代表了一个可看见的多边形区域&#xff08;通常来说是矩形&#xff0…

人工智能学习与实训笔记(六):神经网络之智能推荐系统

人工智能学习笔记汇总链接&#xff1a;人工智能学习与实训笔记汇总-CSDN博客 本篇目录 七、智能推荐系统处理 7.1 常用的推荐系统算法 7.2 如何实现推荐 7.3 基于飞桨实现的电影推荐模型 7.3.1 电影数据类型 7.3.2 数据处理 7.3.4 数据读取器 7.3.4 网络构建 7.3.4.1…

LV.23 D2 开发环境搭建及平台介绍 学习笔记

一、Keil MDK-ARM简介及安装 Keil MDK&#xff0c;也称MDK-ARM&#xff0c;Realview MDK &#xff08;Microcontroller Development Kit&#xff09;等。目前Keil MDK 由三家国内代理商提供技术支持和相关服务。 MDK-ARM软件为基于Cortex-M、Cortex-R4、ARM7、ARM9处理器设备…

《Go 简易速速上手小册》第6章:错误处理和测试(2024 最新版)

文章目录 6.1 错误处理机制 - Go 语言中的优雅回旋6.1.1 基础知识讲解6.1.2 重点案例&#xff1a;文件读取器功能描述实现代码 6.1.3 拓展案例 1&#xff1a;网络请求处理器功能描述实现代码 6.1.4 拓展案例 2&#xff1a;数据库查询执行器功能描述实现代码 6.2 编写可测试的代…

C++类和对象-多态->案例1计算器类、案例2制作饮品、案例3电脑组装需求分析和电脑组装具体实现

#include<iostream> using namespace std; #include<string> //分别利用普通写法和多态技术实现计算器 //普通写法 class Calculator { public: int getResult(string oper) { if (oper "") { return m_Num1 m_Num2; …

[AIGC_coze] Kafka 的主题分区之间的关系

Kafka 的主题分区之间的关系 在 Kafka 中&#xff0c;主题&#xff08;Topics&#xff09;和分区&#xff08;Partitions&#xff09;是两个重要的概念&#xff0c;它们之间存在着密切的关系。 主题是 Kafka 中用于数据发布和订阅的逻辑单元。每个主题可以包含多个分区&#x…