最短路径算法(Dijkstra算法 + Bellman-Ford 算法 + Floyd-Warshall算法)

最短路径算法

完整版万字原文见史上最全详解图数据结构

1. Dijkstra算法(单源最短路径)(无负权边图)


算法原理

1. Dijkstra 算法通过 贪心策略 计算从一个源顶点到其他所有 顶点的最短路径。2. 时间复杂度为 O(V^2)(未优化时)或 O((V + E) log V)(使用优先队列时)3. 应用:适用于无负权边的图。4. 核心思想(1) 选定一个点,这个点满足两个条件:a.未被选过,b.距离最短(2) 对于这个点的所有邻近点去尝试松弛

实现代码(未优化版本)

1


// 求最短路径
// Dijkstra 不断选择当前距离源节点最近的未处理节点来构建最短路径。
// 贪心算法
// 时间复杂度 O(V^2)
void MyGraph::Dijkstra(int startVertex)
{//代表某个顶点是否被访问过,初始化所有的顶点为 falsevector<bool> visited(n, false);//dis代表源点到其它点的最短距离,numeric_limits<int>::max()代表无穷vector<int> distances(n, numeric_limits<int>::max()); //向量 prev 用来存储路径的前驱节点,用于之后路径重建,初始化为 -1vector<int> prev(n, -1);源点到源点的距离为 0distances[startVertex] = 0;//为什么只要寻找n-1个点呢?因为当剩下一个点的时候,这个点已经没有需要松弛的邻接点了for (int i = 0; i < n - 1; i++){//进入循环之后,一开始不知道哪个是没有被访问过且距离源点最短的int now_minDistance = numeric_limits<int>::max();int now_minVertex = -1;//使用这个循环开始寻找没有被访问过且距离源点最短距离的点for (int j = 0; j < n; j++)if (!visited[j] && distances[j] < now_minDistance)now_minDistance = distances[j], now_minVertex = j;//标记当前节点已访问visited[now_minVertex] = true;//对这个距离源点最短距离的点的所有邻接点进行松弛for (const auto &pair : adjList[now_minVertex]){//  松弛操作(Relaxation)if (distances[now_minVertex] + pair.second < distances[pair.first]){distances[pair.first] = distances[now_minVertex] + pair.second, prev[pair.first] = now_minVertex;}}}for (int i = 0; i < n; i++)if (i != startVertex)cout << "vertex " << i << " distance from " << startVertex << " is " << distances[i] << endl;}

实现代码(优化版本)


算法原理:

利用了优先队列(通常是最小堆)将时间复杂度降低到 O((V + E) log V)。

void MyGraph::Dijkstra_withOptimize(int startVertex)
{vector<bool> visited(n, false);                       vector<int> distances(n, numeric_limits<int>::max()); vector<int> prev(n, -1);                              priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;pq.push({0, startVertex});distances[startVertex] = 0; // for(int i = 0; i < n - 1; i++){while (!pq.empty()){// int now_minDistance = numeric_limits<int>::max();// int now_minVertex = -1;int now_minDistance = pq.top().first;int now_minVertex = pq.top().second;pq.pop();// for(int j = 0; j < n; j++) if(!visited[j] && distances[j] < now_minDistance) now_minDistance = distances[j], now_minVertex = j; //don't need amymorevisited[now_minVertex] = true;for (const auto &neighbor : adjList[now_minVertex]){// int neighborDistance = distances[now_minVertex] + neighbor.second;int neighborDistance = now_minDistance + neighbor.second;// if(neighborDistance < distances[neighbor.first]) distances[neighbor.first] = neighborDistance, prev[neighbor.first] = now_minVertex;if (neighborDistance < distances[neighbor.first]){distances[neighbor.first] = neighborDistance;prev[neighbor.first] = now_minVertex;pq.push({neighborDistance, neighbor.first});}}}for (int i = 0; i < n; i++)if (i != startVertex)cout << "vertex " << i << " distance from " << startVertex << " is " << distances[i] << endl;
}

2. Bellman-Ford 算法(单源最短路径)(负边权图)


算法原理

1. Bellman-Ford 算法 ——> 带负权边的图2. 时间复杂度是 (O(n \times E)),其中 (n) 是顶点数,(E) 是边数。3. Bellman-Ford算法的主要思想通过最多 (n - 1) 次松弛操作(Relaxation),逐步逼近最短路径。松弛操作会不断尝试更新路径,直到无法再优化路径为止。4. 如果图中存在负权回路(负权环)的情况,这种情况是求不出最短路径的!       但 Bellman-Ford 算法可以对负权回路的情况进行检测负权环检测:经过 V-1 次松弛操作后,如果还存在可以松弛的边,说明图中存在负权环

算法步骤

1.初始化:dist[start] = 0,其他所有节点的距离 dist[] = ∞2. 松弛操作:对每条边 (u, v) 进行 V-1 次松弛操作。如果 dist[u] + weight < dist[v],则更新 dist[v]。3.负权环检测:对每一条边 (u, v) 进行一次松弛操作。如果有边 (u, v) 能够进一步放松,说明图中存在负权环需要注意的是,以 S 点为源点跑 Bellman–Ford 算法时,如果没有给出存在负环的结果,只能说明从 S 点出发不能抵达一个负环,而不能说明图上不存在负环。因此如果需要判断整个图上是否存在负环,最严谨的做法是建立一个超级源点,向图上每个节点连一条权值为 0 的边,然后以超级源点为起点执行 Bellman–Ford 算法。

代码实现

void MyGraph::BellmanFord(int startVertex)
{// 1.初始化vector<int> distances(n, numeric_limits<int>::max());distances[startVertex] = 0;// 2. 松弛操作// 对每条边 (u, v) 进行 V-1 次松弛操作for (int i = 0; i < n - 1; i++)for (int j = 0; j < n; j++)for (const auto &pair : adjList[j])// 如果 dist[u] + weight < dist[v],则更新 dist[v]if (distances[j] != numeric_limits<int>::max() && distances[j] + pair.second < distances[pair.first])distances[pair.first] = distances[j] + pair.second;// 3.负权环检测// 对每一条边 (u, v) 进行一次松弛操作。for (int j = 0; j < n; j++)for (const auto &pair : adjList[j])// 如果有边 (u, v) 能够进一步放松,说明图中存在负权环if (distances[j] != numeric_limits<int>::max() && distances[j] + pair.second < distances[pair.first]){cout << "Graph contains a negative weight cycle!" << endl;return;}//打印输出for (int i = 0; i < n; i++)if (i != startVertex)cout << "vertex " << i << " distance from " << startVertex << " is " << distances[i] << endl;cout << endl;
}

比较 Bellman-Ford 算法 和 Dijkstra 算法的工作机制

(1)关键区别:

1. Dijkstra 算法:贪心策略每次选择尚未处理的最小距离顶点,然后对其邻接顶点进行松弛操作。优先队列(通常是最小堆)优化2. Bellman-Ford 算法:动态规划算法对图中的每条边执行 n - 1 次松弛操作来计算从源节点到其他所有节点的最短路径。

(2)为什么 Bellman-Ford 不需要找最小值?

    a 遍历所有边的松弛操作:Bellman-Ford 通过对所有边进行遍历和松弛操作,它保证了在最多 n-1 轮迭代后,所有的最短路径都会被正确计算出来。n-1 轮的原因是:在最坏的情况下,最短路径最多需要经过 n-1 条边。b 负权边的处理:Dijkstra 无法处理负权边,而 Bellman-Ford 可以处理负权边。由于负权边的存在,单纯选择最小值无法保证最优解,因此 Bellman-Ford 不像 Dijkstra 那样依赖每步选择最小值顶点。

(3)为什么由于负权边的存在,Bellman-Ford 如果单纯选择最小值无法保证最优解?


2. Floyd-Warshall算法(多源最短路径)

算法原理:

1. 动态规划2. 所有顶点对之间的最短路径。3. 三重循环(引入中间节点,逐步优化路径)4. 时间复杂度为 O(n^3)5. 适用于任何图,不管有向无向,边权正负,但是最短路必须存在。(不能有个负环)

算法步骤

1. 初始化如果有一条边 (i, j),则 dist[i][j] = w(i, j),即边的权重。如果没有边,则 dist[i][j] = ∞。对于同一节点 i,初始化 dist[i][i] = 0,因为从一个节点到自身的距离为0。2. 迭代更新对于每一对节点 (i, j),检查是否通过中间节点 k 可以得到更短的路径即判断是否 dist[i][j] > dist[i][k] + dist[k][j]如果是,则更新 dist[i][j] 为新的最短路径 dist[i][k] + dist[k][j]3. 终止条件:在经过 V 次迭代(每次迭代考虑一个新的中间节点 k)后,dist[i][j] 就是节点 i 到节点 j 的最短路径

实现代码


void MyGraph::FloydWarshall()
{// 1. 初始化// 创建一个二维向量 distances 用来存储两点之间的路径长度,初始化为顶点数量 n 大小,每个元素设置为无穷大(代表两点之间没有路径)vector<vector<int>> distances(n, vector<int>(n, numeric_limits<int>::max()));// 创建一个二维向量 prev 用来存储路径的前驱节点,用于之后路径重建,初始化为 -1vector<vector<int>> prev(n, vector<int>(n, -1));// 所有节点到自己的距离为 0,因此将 distances[i][i] 设为 0。for (int i = 0; i < n; i++)distances[i][i] = 0;// 初始化相邻节点之间的距离。遍历每个节点 i 的邻接表 adjList[i],将边的权重 edge.second 设置为相应的 distances[i][edge.first]// 将 prev[i][edge.first] 设置为 i,表示 i 是 edge.first 的前驱。for (int i = 0; i < n; i++)for (const auto &edge : adjList[i])distances[i][edge.first] = edge.second, prev[i][edge.first] = i;// 2. 迭代更新// Floyd-Warshall 的核心!!!// 通过引入中间节点 k,检查从节点 i 经过 k 再到 j 的路径是否比 i 直接到 j 更短。如果是,则更新 distances[i][j] 和 prev[i][j]for (int k = 0; k < n; k++)for (int i = 0; i < n; i++)for (int j = 0; j < n; j++)if (distances[i][k] != numeric_limits<int>::max() && distances[k][j] != numeric_limits<int>::max() && distances[i][k] + distances[k][j] < distances[i][j])distances[i][j] = distances[i][k] + distances[k][j], prev[i][j] = prev[k][j];// 输出所有顶点对之间的最短路径for (int i = 0; i < n; i++)for (int j = 0; j < n; j++)if (i != j)cout << "Vertex " << i << " to " << j << " distance is " << distances[i][j] << endl;
}

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

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

相关文章

pyqt6事件概要

例子&#xff1a; 利用qtdesigner建立闹钟 python代码 # 导入所需要的文件 from PyQt6.QtGui import QIcon, QPixmap from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton, QListWidgetItem from PyQt6 import uic from PyQt6.QtCore import Qt, QTime imp…

位运算符I^~

&运算&#xff1a;上下相等才是1&#xff0c;有一个不同就是0 |运算&#xff1a;只要有1返回的就是1 ^(亦或)运算&#xff1a;上下不同是1&#xff0c;相同是0 ~运算&#xff1a;非运算&#xff0c;与数据全相反 cpu核心运算原理&#xff0c;四种cpu底层小电路 例&#xf…

【求助】Tinymce组件异常

版本号 { "tinymce/tinymce-vue": "^3.0.1", "tinymce": "^5.10.9", "vue": "^2.6.10", }问题&#xff1a; 就是红框处点击后没有菜单出现&#xff0c;下面是正常的

大模型分布式训练框架——DeepSpeed

本文的主要内容是阐述DeepSpeed训练模块在跟进大模型技术中的作用&#xff0c;重点解析了RLHF在其中的应用。文中深入探讨了模型训练的关键概念&#xff0c;如显存需求、优化器迭代和混合精度训练。针对大规模模型训练&#xff0c;介绍了模型并行和流水线并行等分布式训练方法&…

跟着AI 学 AI, 开发一个ChatBot, 完整图文版__持续更新中

跟着AI 学 AI&#xff0c; 开发一个ChatBot, 完整图文版__持续更新中.记录步骤以便排查错误。 使用Python 加 Visual Studio Code&#xff0c;开发代码。 使用Flask 套件和 ngrok 工具。 Step 1 下载安装Python &#xff0c;下载完后 在CMD 测试 Python --version. 结果出现p…

Pyside6 --Qt Designer--Qt设计师--了解+运行ui_demo_1.py

目录 一、打开Qt设计师1.1 Terminal终端1.2 打开env&#xff0c;GUI虚拟环境下的scripts文件1.3 不常用文件介绍&#xff08;Scripts下面&#xff09; 二、了解Qt设计师的各个控件作用2.1 点击widget看看效果&#xff01;2.2 点击Main Window看看效果 三、编写一个简易的UI代码…

『大模型笔记』OpenAI 十二天活动第1天:o1和o1 pro

『大模型笔记』OpenAI 十二天活动第1天:o1和o1 pro 文章目录 一. 『大模型笔记』OpenAI 十二天活动第1天:o1和o1 proOpenAI的12天活动o1完整版本的发布o1 Pro模式ChatGPT Proo1的性能提升多模态输入与推理o1 Pro模式的应用模型对话与历史问题示范二. 参考文献一. 『大模型笔记…

SpringBoot 运行发生异常:java: 错误: 不支持发行版本 5

一、异常&#xff1a; 二、原因&#xff1a; 本地运行用的是JDK17&#xff0c;报错应该是项目编译配置使用的Java版本不对&#xff0c;需要检查一下项目及环境使用的Java编译版本配置。 三、解决&#xff1a;

2024.12.2——[极客大挑战 2019]Secret File 1

知识点&#xff1a;抓包 代码审计 filter伪协议 一、解题步骤 step 1 查看源代码中的信息 查看源代码发现一个php文件&#xff1a;[./Archive_room.php](http://72df1f22-85bf-47bb-b23a-efcaf88701d4.node5.buuoj.cn:81/Archive_room.php) 点进去后发现没什么用&#xff0c…

MKS EDGE Series RF Generators Power Solution 软件

MKS EDGE Series RF Generators Power Solution 软件

【汇编语言】标志寄存器(一) —— 标志寄存器中的标志位:ZF、PF、SF、CF、OF 一网打尽

前言 &#x1f4cc; 汇编语言是很多相关课程&#xff08;如数据结构、操作系统、微机原理&#xff09;的重要基础。但仅仅从课程的角度出发就太片面了&#xff0c;其实学习汇编语言可以深入理解计算机底层工作原理&#xff0c;提升代码效率&#xff0c;尤其在嵌入式系统和性能优…

【C++】priority_queue优先队列

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解C的string类的priority_queue优先队列&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 1. 介绍2. 仿函数(A) 介绍(B) 控制比较逻辑 3. priority_queue和…

Python3 operator 模块

Python2.x 版本中&#xff0c;使用 cmp() 函数来比较两个列表、数字或字符串等的大小关系。 Python 3.X 的版本中已经没有 cmp() 函数&#xff0c;如果你需要实现比较功能&#xff0c;需要引入 operator 模块&#xff0c;适合任何对象&#xff0c;包含的方法有&#xff1a; o…

短视频矩阵系统开发|技术源代码部署

短视频矩阵系统通过多账号运营管理、多平台视频智能分发等功能&#xff0c;助力企业实现视频引流、粉丝沉淀和转化。 短视频矩阵系统是一种创新的营销工具&#xff0c;它整合了多账号管理、视频智能分发、数据可视化等多种功能&#xff0c;为企业在短视频领域的发展提供了强大…

YOLOV11 快速使用教程

概述 这里主要记录使用NVIDIA GPU pytorch 检测系列模型的快速使用方式&#xff0c;可以快速解决一些工业应用的问题&#xff0c;比如&#xff1a;无网、数据大需要改路径、需要记录不同实验结果等问题。 安装 参考官网&#xff0c;自己安装好Python > 3.8和pytorch >…

git修改某次commit(白痴版)

第一步 在bash窗口运行 git rebase --interactive commitId^ 比如要改的commitId是 abcedf git rebase --interactive abcedf^键盘 按 i 或者 ins 进入编辑状态 进入insert 编辑状态 在bash窗口手动把对应commit前面的pick改为e或edit 按 esc 进入退出程序 输入 :wq 保存退出…

AI 建站:Durable

网址&#xff1a;https://app.durable.co 步骤 1) 登录 2&#xff09;点击创建新业务 3&#xff09;填写信息后&#xff0c;点击创建 4&#xff09;进入业务 5&#xff09;生成网站 6&#xff09;生成完成后不满意的话可以自己调整 7&#xff09;点击保存 8&#xff09;发布 …

网络原理之 TCP 协议

目录 1. TCP 协议格式 2. TCP 原理 (1) 确认应答 (2) 超时重传 (3) 连接管理 a) 三次握手 b) 四次挥手 (4) 滑动窗口 (5) 流量控制 (6) 拥塞控制 (7) 延时应答 (8) 捎带应答 3. TCP 特性 4. 异常情况的处理 1) 进程崩溃 2) 主机关机 (正常流程) 3) 主机掉电 (…

Python爬虫之selenium库驱动浏览器

目录 一、简介 二、使用selenium库前的准备 1、了解selenium库驱动浏览器的原理 &#xff08;1&#xff09;、WebDriver 协议 &#xff08;2&#xff09;、 浏览器驱动&#xff08;Browser Driver&#xff09; &#xff08;3&#xff09;、 Selenium 客户端库 &#xff0…

Vite+Vue3项目实战:组件化开发与通信指南

一、典型的ViteVue3项目结构 续上文成功创建Vue3项目的脚手架&#xff0c;通过visual Studio Code软件打开刚刚创建的文件夹&#xff0c;将会看到这样一个项目结构。 使用Vite构建Vue3项目时&#xff0c;项目结构通常遵循一定的组织规则&#xff0c;以保持代码的清晰和可维护性…