探索数据结构:图(三)之最短路径算法


✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:数据结构与算法
贝蒂的主页:Betty’s blog

1. 最短路径算法

最短路径问题可分为单源最短路径多源最短路径。其指的是在带权有向图中,从某一顶点出发,找出通往另一顶点的路径,其中“最短”意味着该路径各边的权值总和达到最小。
其中单源最短路径是从图中的某一特定顶点出发,通往其他所有顶点的最短路径。与之不同,多源最短路径则是找出图中任意两个顶点之间的最短路径。

1.1 单源最短路径

解决单源最短路径的方法常见的有两种:Dijkstra(迪杰斯特拉算法)Bellman-Ford(贝尔曼福特算法)

1.1.1 Dijkstra

Dijkstra算法(迪杰斯特拉算法)是用于求解带权有向图中单源最短路径问题的算法。该算法以起始顶点为中心向外层层扩展,直到扩展到终点为止。它通过维护一个距离源点距离最短的顶点集合,每次从尚未确定最短路径的顶点中选择一个距离源点最近的顶点,将其加入到集合中,并更新与其相邻顶点到源点的距离。但是需要注意的是迪杰斯特拉算法要求权值为正数,如果有负数则可能出错。
二、算法步骤

  1. 初始化:
  • 把图中所有顶点分为两部分,一部分是已确定最短路径的顶点集合(初始时只有源点),另一部分是未确定最短路径的顶点集合。
  • 为每个顶点设置一个距离值,表示从源点到该顶点的当前最短路径长度(初始时,源点的距离值为 0,其他顶点的距离值为无穷大)。
  1. 重复以下步骤直到所有顶点都被加入到已确定最短路径的集合中:
  • 从未确定最短路径的顶点集合中选择一个距离源点最近的顶点u
  • 将顶点u加入到已确定最短路径的集合中。
  • 对于与顶点u相邻的每个顶点v,更新其距离值。如果通过顶点u到达顶点v的路径比当前已知的最短路径更短,则更新顶点v的距离值为从源点到顶点u的距离加上边<u,v>的权值。
  1. 算法结束后,每个顶点的最终距离值就是从源点到该顶点的最短路径长度。
  1. 首先选择s点作为起始点更新,更新两个离的最近ty顶点。

  1. 接下来选择已更新节点权值最小的顶点y出发继续更新顶点,首先t顶点会被再次更新,接着xz顶点也会被更新。同理我们继续选择顶点z作为起始点,会再次更新x节点。

  1. 最后选择t顶点再次更新x顶点,最后选择x顶点更新完毕。


接下来我们需要实现Djikstra算法,首先我们可以创建一个pPath数组来记录到达各个顶点的前驱顶点,方便最后得出最短路径。然后进行初始化,并且创建一个bool数组来标记已确定加入集合的顶点,然后遍历所有顶点选择未加入集合且当前距离源点最小的顶点开始更新距离,更新之前先将该顶点标记。

//Dijkstra算法
void Dijkstra(const V& src, vector<W>& dist, vector<int>& pPath)
{int n = _vertexs.size();int srci = getVertexsIndex(src);//获取源点下标dist.resize(n, MAX_W);//初始化pPath.resize(n, -1);//默认父节点下标为-1dist[srci] = W();//源点设为初始值vector<bool> visited(n, false);//已选中节点//更新n个顶点for (int i = 0; i < n; i++){W minW = MAX_W;int u = -1;for (int j = 0; j < n; j++){//选出距离的边记录其下标与权值if (visited[j] == false && dist[j] < minW){minW = dist[j];u = j;}}//标记visited[u] = true;//进行松弛更新for (int v = 0; v < n; v++){if (visited[v] == false && _matrix[u][v] != MAX_W && dist[u] + _matrix[u][v] < dist[v]){dist[v] = dist[u] + _matrix[u][v];pPath[v] = u;}}}
}

然后我们可以通过父节点更新出最短路径。

void PrinrtShotPath(const V& src, const vector<W>& dist, const vector<int>& pPath)
{int n = _vertexs.size();int srci = getVertexsIndex(src);for (int i = 0; i < n; i++){vector<int> path;int cur = i;while (cur != -1){path.push_back(cur);cur = pPath[cur];}reverse(path.begin(), path.end());for (int j = 0; j < path.size(); j++){cout << _vertexs[path[j]] << "->";}cout << "权值为:" << dist[i] << endl;}
}
1.1.2 Bellman-Ford

Bellman-Ford算法也是用于求解带权有向图中单源最短路径问题的算法。与Dijkstra算法不同的是,Bellman-Ford算法可以处理图中存在负权边的情况。
算法步骤:

  1. 初始化:
  • 为每个顶点设置一个距离值,表示从源点到该顶点的当前最短路径长度(初始时,源点的距离值为 0,其他顶点的距离值为无穷大)。
  1. 重复以下步骤V - 1次(V 是图中顶点的数量):
  • 对于图中的每一条边(u, v),如果从源点到顶点u的距离加上边(u, v)的权值小于当前从源点到顶点v的距离,则更新顶点v的距离值为从源点到顶点u的距离加上边 (u, v) 的权值。
  1. 检测负权回路(回路权值为负):
  • 再对图中的每一条边进行一次遍历。如果在这次遍历中,还能找到一条边(u, v),使得从源点到顶点u 的距离加上边(u, v)的权值小于当前从源点到顶点 v 的距离,那么说明图中存在负权回路。
  1. 算法结束后,如果没有负权回路,每个顶点的最终距离值就是从源点到该顶点的最短路径长度;如果存在负权回路,则无法得到正确的最短路径结果。
  1. 选取顶点s作为起始点,开始更新s-ts-y的距离。

  1. 分别以顶点yt作为起始点更新到达顶点xz的距离。

  1. 最后以顶点xz点作为起始点更新距离,其中因为到达顶点t的最小距离s-t被再次更新,所以到达顶点z的最小距离s-t-z也需要被再次更新。


下来我们需要实现Bellman-Ford算法,同样我们可以创建一个pPath数组来记录到达各个顶点的前驱顶点,方便最后得出最短路径。然后只需要循环遍历每一个顶点,以该顶点为起始点更新其他顶点,最后重复V-1轮即可。

bool BellmanFord(const V& src, vector<W>& dist, vector<int>& pPath)
{int n = _vertexs.size();//获取源点下标int srci = getVertexsIndex(src);//初始化dist.resize(n, MAX_W);pPath.resize(n, -1);dist[srci] = W();//至多更新n-1轮for (int v = 0; v < n - 1; v++){//判断是否发生更新bool update = false;for (int i = 0; i < n; i++){for (int j = 0; j < n; j++){if (_matrix[i][j] != MAX_W && dist[i] != MAX_W && dist[i] + _matrix[i][j] < dist[j]){dist[j] = dist[i] + _matrix[i][j];pPath[j] = i;update = true;}}}//如果未发生则直接退出if (update == false){break;}}//判断是否存在负权回路for (int i = 0; i < n; i++){for (int j = 0; j < n; j++){if (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j]){return false;//存在负权回路}}}
}

Bellman-Ford算法还有一个优化方案叫做SPFA(Shortest Path Faster Algorithm),主要是用一个队列来维护可能需要再次的顶点,避免了冗余的过程,但是这里不再重点介绍,感兴趣可以自行了解。
思考题:为什么要重复更新V-1次?

因为每一次更新可能使得前面已经更新过的路径的长度可以变得更短,或者使得某些源顶点之前不可达的顶点变得可达。比如说s->t->x先被更新,但是后续可能存在其他路径使得s->t路径更小,此时却并没有更新s->t->x路径。所以每一次更新只能使得至少能确定最短路径中的一条边,一个有V-1条边,所以至多更新V-1次。

1.2 多源最短路径

解决多源最短路径问题,虽然可以通过遍历每一个顶点使用DijkstraBellman-Ford算法,但明显时间复杂度太高。而最常见的解决该问题便是Floyd-Warshall(弗洛伊德算法),但是学习这种算法需要有点门槛,你得需要了解什么是动态规划

1.2.1 Floyd-Warshall

Floyd-Warshall算法的原理基于动态规划。设 D i j D_{ij} Dij 为从 i i i j j j 只以 1.. k 1..k 1..k集合中的节点为中间节点的最短路径长度。

  1. 若最短路径经过点 k k k,则 D i , j , k = D i , k , k − 1 + D k , j , k − 1 D_{i,j,k} = D_{i,k,k-1} + D_{k,j,k-1} Di,j,k=Di,k,k1+Dk,j,k1
  2. 若最短路径不经过点 k k k ,则 D i , j , k = D i , j , k − 1 D_{i,j,k} = D_{i,j,k-1} Di,j,k=Di,j,k1

因此, D i , j , k = min ⁡ ( D i , j , k − 1 , D i , k , k − 1 + D k , j , k − 1 ) D_{i,j,k} = \min(D_{i,j,k-1},D_{i,k,k-1} + D_{k,j,k-1}) Di,j,k=min(Di,j,k1,Di,k,k1+Dk,j,k1) 。在实际算法中,为节约空间,可直接在原空间迭代,空间可降至二维。


路径 p p p是从结点 i i i到结点 j j j的一条最短路径,结点 k k k是路径 p p p上编号最大的中间结点。路径 p 1 p1 p1是路径 p p p上从结点 i i i到结点 k k k之间的一段,其所有中间结点取自集合 ( 1 , 2 , … , k − 1 ) (1,2,…,k - 1) (1,2,,k1)。从结点 k k k到结点 j j j的路径 p 2 p2 p2也遵守同样的规则。
算法步骤:

  1. 初始化:
  • 构建两个二维数组,一个用于存储最短路径长度估计值 D i j D_{ij} Dij,初始时,若两点之间有直接边则为边的权值,否则为无穷大;另一个数组用于记录路径的前驱节点。
  1. 动态更新:
  • 对于每个中间顶点 k k k,遍历所有的顶点对 i i i j j j。如果 D i k + D k j < D i j D_{ik} + D_{kj} < D_{ij} Dik+Dkj<Dij,则更新 D i j D_{ij} Dij D i k D_{ik} Dik + D k j D_{kj} Dkj,并更新前驱节点。

下来我们需要实现Floyd-Warshall算法,同样我们可以创建一个pPath数组来记录到达各个顶点的前驱顶点,方便最后得出最短路径。然后只需要循环遍历每一个顶点,以该顶点为中间顶点更新其他顶点对。

//FloydWarshall
void FloydWarshall(vector<vector<W>>& dist, vector<vector<int>>& pPath) 
{//初始化int n = _vertexs.size();dist.resize(n, vector<W>(n, MAX_W)); pPath.resize(n, vector<int>(n, -1)); //初始化直接相连的边for (int i = 0; i < n; i++){for (int j = 0; j < n; j++) {if (_matrix[i][j] != MAX_W) { dist[i][j] = _matrix[i][j]; pPath[i][j] = i; }//顶点本身if (i == j) { dist[i][j] = W(); }}}//依次取每个顶点作为中间节点for (int k = 0; k < n; k++) { //起始顶点for (int i = 0; i < n; i++) {//结束顶点for (int j = 0; j < n; j++) {// i->k + k->j 比 i->j前面更新的距离更短,则更新if (dist[i][k] != MAX_W && dist[k][j] != MAX_W &&dist[i][k] + dist[k][j] < dist[i][j]) { dist[i][j] = dist[i][k] + dist[k][j]; pPath[i][j] = pPath[k][j]; }}}}
}

2. 复杂度分析

2.1 Dijkstra

时间复杂度:

时间复杂度为 O ( N 2 ) O(N^2) O(N2),这里的 N N N 代表图中顶点的数量。具体原因如下:

  • 使用邻接矩阵存储图时,每次从未确定最短距离的顶点中找到距离最小的顶点需要 O ( N ) O(N) O(N)的时间,而总共要进行 N N N次这样的操作。对于每个确定了最短距离的顶点,更新其邻接顶点的距离也需要 O ( N ) O(N) O(N)的时间,因为需要遍历所有顶点。所以总的时间复杂度为 O ( N 2 ) O(N^2) O(N2)

空间复杂度:

空间复杂度为 O ( N ) O(N) O(N)。主要原因是需要存储每个顶点到源点的距离以及该顶点是否已确定最短距离等信息。对于有 N N N 个顶点的图,这些信息总共需要 O ( N ) O(N) O(N) 的空间。具体来说:

  • 需要一个标记数组来记录每个顶点是否已确定最短距离,这个数组的大小也为 N N N
  • 需要一个数组来存储每个顶点到源点的距离,这个数组的大小为 N N N

2.2 Bellman-Ford

时间复杂度:

总体时间复杂度为 O ( N × E ) O(N\times E) O(N×E),其中 N N N 是图中顶点的数量, E E E 是图中边的数量。

  • 分析:Bellman-Ford算法需要对图中的边进行 N − 1 N - 1 N1 轮遍历,每一轮遍历所有的边,以松弛操作来更新最短路径。对于每一轮,遍历所有边的时间复杂度为 O ( E ) O(E) O(E),而总共进行 N − 1 N - 1 N1 轮,所以时间复杂度为 O ( N × E ) O(N\times E) O(N×E)
  • 当使用邻接矩阵实现时,遍历图中的所有边的时间复杂度变为 O ( N 2 ) O(N^2) O(N2),从而导致上述代码的时间复杂度变为 O ( N 3 ) O(N^3) O(N3)

空间复杂度:

空间复杂度为 O ( N ) O(N) O(N)

  • 分析:主要需要存储每个顶点到源点的最短距离,以及一些辅助信息,这些信息总共需要 O ( N ) O(N) O(N) 的空间。对于有 N N N 个顶点的图,存储每个顶点的最短距离需要 N N N 个空间,同时可能还需要一些额外的空间来存储中间状态等信息,但在整体空间复杂度中,占主导地位的仍然是存储顶点最短距离的空间,所以空间复杂度为 O ( N ) O(N) O(N)

2.3 Floyd-Warshall

时间复杂度:

总体时间复杂度为 O ( N 3 ) O(N^3) O(N3),其中 N N N 是图中顶点的数量。

  • 分析:Bellman-Ford算法需要对图中的边进行 N − 1 N - 1 N1 轮遍历,每一轮遍历所有的边,以松弛操作来更新最短路径。对于每一轮,遍历所有边的时间复杂度为 O ( E ) O(E) O(E),而总共进行 N − 1 N - 1 N1 轮,所以时间复杂度为 O ( N × E ) O(N\times E) O(N×E)
  • 当使用邻接矩阵实现时,遍历图中的所有边的时间复杂度变为 O ( N 2 ) O(N^2) O(N2),从而导致上述代码的时间复杂度变为 O ( N 3 ) O(N^3) O(N3)

空间复杂度:

空间复杂度为 O ( N ) O(N) O(N)

  • 分析:主要需要存储每个顶点到源点的最短距离,以及一些辅助信息,这些信息总共需要 O ( N ) O(N) O(N) 的空间。对于有 N N N 个顶点的图,存储每个顶点的最短距离需要 N N N 个空间,同时可能还需要一些额外的空间来存储中间状态等信息,但在整体空间复杂度中,占主导地位的仍然是存储顶点最短距离的空间,所以空间复杂度为 O ( N ) O(N) O(N)

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

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

相关文章

《机器学习》 SVM支持向量机 推导、参数解析、可视化实现

目录 一、SVM支持向量机 1、什么是SVM 例如&#xff1a; 2、SVM的主要特点是&#xff1a; 二、SVM方程 1、超平面方程 2、标签问题 3、决策函数&#xff1a; 符号函数&#xff1a; 整合&#xff1a; 4、距离问题 1&#xff09;点到直线距离 2&#xff09;点到平面…

航空公司名字趣史:看看有趣又有意义的命名背后有什么玄机

上周“东海航空”事件引发了东方航空在社交媒体上的一系列被迫营业&#xff0c;因为媒体的乌龙报道误将“东海航空”简称为“东航”&#xff0c;甚至直接用错了图片。众号&#xff1a;标猿公司起名 给公司起个好名字 其实除了大部分以地域、国家命名的航空公司&#xff0c;还…

Android Auto推出全新Google助手设计

智能手机与汽车的无缝整合已成为现代驾驶的重要组成部分&#xff0c;而 Android Auto 一直在这一领域处于领先地位。谷歌通过不断推出新功能和更新&#xff0c;体现了其致力于提升 Android Auto 体验的决心。最近&#xff0c;Android Auto 引入了 Google助手的全新设计。 当系…

【Qt】多元素控件QTreeWidget

多元素控件QTreeWidget 使用QTreeWidget表示一个树型结构&#xff0c;里面的每一个元素都是QTreeWidgetItem&#xff0c;每个QTreeWidgetItem可以包含多个文本和图标&#xff0c;每个文本/图标表示一列。 可以给QTreeWidget设置顶层结构&#xff08;顶层节点可以有多个&#…

redis面试(二十二)读锁释放

假设现在已经有各种锁的重入什么的&#xff0c;那如何释放锁&#xff1f; 读锁读锁 假如说&#xff0c;同一个线程多次加读锁&#xff0c;或者不同的线程加了多个读锁 当前的锁结构长这样 anyLock: { “mode”: “read”, “UUID_01:threadId_01”: 2, “UUID_02:threadId_02…

去雾去雨算法

简单版 import cv2 import numpy as npdef dehaze(image):"""简单去雾算法&#xff0c;使用直方图均衡化来增强图像"""# 将图像转换为YUV颜色空间yuv_image cv2.cvtColor(image, cv2.COLOR_BGR2YUV)# 对Y通道&#xff08;亮度&#xff09;进行…

数据结构——队的基本操作

一、顺序队 队的用法&#xff1a;先进先出 跟平时我们遇到的大多情况一样&#xff0c;队的主要思想就是先进先出&#xff0c;比如我去食堂打饭&#xff0c;我先排那么就是我先打到饭咯 顺序队&#xff1a;其实说白了就是一块空间用两个指针去指向&#xff0c;为了实现先进先…

C语言指针重学

学习要纲:建议掌握 gdb调试(b ,d ,fin ,bt ,print ,awatch ,up ,down ,set pretty等) SourceInsight软件看代码(全局搜索 文件搜索等) git如何调取分支合并(git branch,git blame,git log,git pull,git reset --hard等) 等内容,下面是对于指针的一个重新学习. C语言的指针&…

AI工具 GPT 学术优化 (GPT Academic) 安装实践

GPT 学术优化 (GPT Academic)是一个综合的AI GPT工具包&#xff0c;可以完成各种gpt辅助的工作&#xff0c;比如代码解读、翻译、读论文等功能。官网&#xff1a;GitHub - binary-husky/gpt_academic: 为GPT/GLM等LLM大语言模型提供实用化交互接口&#xff0c;特别优化论文阅读…

2024年中国运筹学会运筹竞赛(数据驱动赛道)报名通知

竞赛组织 主办单位&#xff1a;中国运筹学会&#xff08;国家一级学会&#xff09; 承办单位&#xff1a;中国科学技术大学 支持单位&#xff1a;杉数科技、海康威视、中国科学技术大学管理学院、《运筹学学报》杂志 竞赛内容 本次竞赛&#xff08;本科生组&#xff09;由竞…

BOSS直聘财报:2024年第二季度净利润4.17亿元,同比上涨34.8%

8月28日美股盘前&#xff0c;BOSS直聘&#xff08;NASDAQ:BZ,HK:2076&#xff09;发布了2024年第二季度财报。在第二季度&#xff0c;公司经营效率不断提升&#xff0c;非通用会计准则下&#xff0c;取得净利润4.17亿元&#xff0c;同比上涨34.8%。 第二季度&#xff0c;公司持…

实习结束总结20240828

长达两个月的实习终于在今天结束了&#xff0c;不知怎的&#xff0c;心如止水&#xff0c;没有高兴&#xff0c;没有伤心&#xff0c;毫无波澜的内心甚至让自己都感觉可怕&#xff0c;也许&#xff0c;这就是成长吧。 硬件上&#xff1a; 1.cadence需要继续深入学习&#xff…

深圳保障房、商品房、小产权房子类型对比

摘要&#xff1a; 整理了我认知以内的深圳房子类型&#xff0c;有安居房&#xff0c;可售人才房&#xff0c;共有产权房、配售型保障房、商品房、统建楼、农民房的区别。如果数据存疑&#xff0c;可以多方对比论证&#xff0c;我也主要靠百度。 我发现我很多同事是非深户&#…

JS WebSocket 深度解析

JS WebSocket 深度解析 文章目录 JavaScript WebSocket 深度解析一、WebSocket 是什么二、JS 中如何使用 WebSocket1. 创建 WebSocket 对象2. 连接打开事件3. 监听消息事件4. 监听错误事件5. 关闭连接 三、WebSocket 包含哪些属性或方法 API1. 属性2. 方法 四、扩展与高级技巧1…

结果一。5.be doing表将来和 表 will的区别

be doing 表⽰近期、眼下就要发⽣的事情; will 表⽰将来的时间,则较远⼀些。如: He is going to write a letter tonight.He will write a book 。 be going to 表⽰根据主观判断将来肯定发⽣的事情。 will+ 动词原形表⽰⼀般将来时。 will ࿰

【xilinx】米联客ZYNQ MZ7100自学发现JTAG烧写失败

3-2-01米联客 2022 版 ZYNQ SOC SDK 入门篇 02 程序固化入门(SDK 方式) 生成了boot.bin 2.4.2 程序通过jtag烧不进去卡在performing erase operation 最终发现是spi的flash type 模式设置错误&#xff0c;文档和板卡没对应上 文档写的qspi-x4-single 实际用的qspi-x8-dual_par…

16:9横屏短视频素材库有哪些?横屏短视频素材网站分享

在当今这个视觉为王的时代&#xff0c;16:9横屏视频凭借其宽阔的画面和卓越的观看体验&#xff0c;已经成为许多视频创作者和营销专家的首选格式。如果你想制作出引人注目的横屏视频&#xff0c;选择高质量的视频素材库是关键。无论你是视频制作的老手还是刚入行的新手&#xf…

免费分享一套SpringBoot+Vue个人理财管理系统【论文+源码+SQL脚本】,帅呆了~~

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的SpringBootVue个人理财管理系统&#xff0c;分享下哈。 项目视频演示 【免费】SpringbootVue个人理财管理系统 Java毕业设计_哔哩哔哩_bilibili 项目介绍 随着信息技术在管理上越来越深入而广泛的应用&am…

【图像去噪】论文复现:代替ReLU!Pytorch实现即插即用激活函数模块xUnit,并插入到DnCNN中实现xDnCNN!

请先看【专栏介绍文章】&#xff1a;【图像去噪&#xff08;Image Denoising&#xff09;】关于【图像去噪】专栏的相关说明&#xff0c;包含适配人群、专栏简介、专栏亮点、阅读方法、定价理由、品质承诺、关于更新、去噪概述、文章目录、资料汇总、问题汇总&#xff08;更新中…

文章生成用这三款伪原创软件效果好

在当今信息爆炸的时代&#xff0c;无论是网站运营者、博主、作家还是学生&#xff0c;对文章的需求量越来越大。他们需要用大理的的原创文章来满足他们工作需求。然而&#xff0c;对于许多人来说&#xff0c;写作一篇优质的文章并非易事。这就产生了一种需求&#xff0c;那就是…