目录

一、前言

二、算法思想

三、代码实现

四、测试

五、源码


一、前言

前两篇学习的Dijkstra算法和Bellman-Ford算法都是用来求解图的单源最短路径,即从图中指定的一个源点出发到图中其他任意顶点的最短路径。Dijkstra算法不能求解带有负权重的图的最短路径,而Bellman-Ford算法弥补了这个缺点。本篇文章再来见识一下一个求解多源最短路径的算法——Floyd-Warshall算法

多源最短路径–Floyd-Warshall算法

Floyd-Warshall算法是一种解决多源最短路径问题(任意两点间的最短路径)的算法。

Floyd算法又称为插点法,是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法(可以求解带负权的图)。 该算法名称以创始人之一、1978年图灵奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德命名。

我们前面学的Dijkstra算法和Bellman-Ford算法,它们是用来求单源最短路径的,但是我们如果以所有的顶点为起点都走一遍Dijkstra/Bellman-Ford算法的话,其实也可以得到任意两点间的最短距离。 不过呢,Dijkstra算法的话不可以求解带负权路径的图,而Bellman-Ford算法呢效率又有点低。  

二、算法思想

Floyd-Warshall算法考虑的是一条最短路径的中间节点

设k是最短路径p的一个中间节点,那么从i到j的最短路径p就被分成i到k和k到j的两段最短路径p1,p2。 p1是从i到k且中间节点属于{1,2,…,k-1}取得的一条最短路径;p2是从k到j且中间节点属于{1,2,…,k-1}取得的一条最短路径

 如何去求最短路径p呢?我们先来了解下面的原理

 即Floyd算法本质是三维动态规划,D[i][j][k]表示从点i到点j只经过0到k个点最短路径,然后建立起转移方程,然后通过空间优化,优化掉最后一维度,变成一个最短路径的迭代算法,最后即得到所有点的最短路。

上面原理中的这个公式,在动态规划中应该叫状态转移方程:

Di,j,k表示从i到j的最短路径,该路径经过的中间结点是剩余的结点组成的集合中的结点,假设经过k个结点,编号为1…k,然后这里就分为了两种情况:

  1. 如果路径经过了结点k,那么ij的距离就等于ik的距离加上kj的距离,然后剩余就经过k-1个点
  2. 如果不经过结点k,那ij的距离就等于i到j经过k-1个点(不包括k)的距离

那i到j的最短路径就等于这两种情况中的最小值

考虑这里如何存储最短路径和路径权重 ?

前面的两个算法中我们的dist数组和pPath数组都是用了一个一维数组就行了。 但是Floyd-Warshall算法就不一样了,因为前两个算法算的是单源最短路径,而Floyd-Warshall算法是多源最短路径。 因为前面我们都是一个起点,然后求其它顶点到起点的最短路径;而现在是多源,即每个顶点都可以是起点,所以我们要记录每个顶点作为起点时到其它顶点的最短路径距离和路径。 那我们就需要用二维数组了。

三、代码实现

初始化部分

void FloydWarshall(vector<vector<W>>& vvDist, vector<vector<int>>& vvpPath)
{size_t n = _vertexs.size();  // 获取图中顶点数量vvDist.resize(n);            // 调整距离矩阵大小为n*nvvpPath.resize(n);           // 调整路径矩阵大小为n*n// 初始化权值和路径矩阵
for (size_t i = 0; i < n; ++i) {vvDist[i].resize(n, MAX_W);  // 距离初始化为无穷大(MAX_W)vvpPath[i].resize(n, -1);    // 路径初始化为无效值(-1)
}

 接着 我们要把图中所有相连的边的信息直接更新一下,因为上面我们说了那个公式叫做状态转移方程,而这里初始化更新的结果就作为起始状态,后面通过状态转移方程不断更新得到最终结果

// 直接相连的边更新
for (size_t i = 0; i < n; ++i) {for (size_t j = 0; j < n; ++j) {if (_matrix[i][j] != MAX_W) {  // 存在直接边vvDist[i][j] = _matrix[i][j];  // 更新直接距离vvpPath[i][j] = i;             // 记录前驱为起点i}if (i == j) {  // 对角线处理vvDist[i][j] = W();  // 自身距离设为0(W()生成零值)}}
}

 根据状态转移方程更新

// 最短路径更新:i-> {其他顶点} ->j
for (size_t k = 0; k < n; ++k) {        // 中间顶点kfor (size_t i = 0; i < n; ++i) {     // 起点ifor (size_t j = 0; j < n; ++j) { // 终点j// 尝试通过k中转更新路径
//如果:
//1. ik是相连的
//2. kj是相连的
//3. ik+kj的距离(经过k点)小于ij(不经过k点)的距离,就更新为ik+kj的距离,否则不更新,保存原来不经过k点的距离
//其实就是前面我们给出的状态转移方程的判断(取两者之间小的那一个) if (vvDist[i][k] != MAX_W && vvDist[k][j] != MAX_W&& vvDist[i][k] + vvDist[k][j] < vvDist[i][j]) {// 更新最短距离vvDist[i][j] = vvDist[i][k] + vvDist[k][j];// 更新路径前驱(关键步骤!)//注意:这里j的上一个结点不能之间给k,因为k->j路径上k和j不一定之间相连,//有可能也更新了中间结点(k->...->j),//而kj路径上的j的上一个是誰?就存储在vvpPath[k][j]里面vvpPath[i][j] = vvpPath[k][j];}}}

四、测试

加一个打印权值和路径的二维数组的代码,因为上面那个例子也是把每一步对应的两个二维数组(矩阵)画了出来,我们可以打印(每个顶点作为中间结点更新之后的都打印一下)出来观察对比一下:

// 打印权值和路径矩阵观察数据for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){if (vvDist[i][j] == MAX_W){//cout << "*" << " ";printf("%3c", '*');}else{//cout << vvDist[i][j] << " ";printf("%3d", vvDist[i][j]);}}cout << endl;}cout << endl;for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){//cout << vvParentPath[i][j] << " ";printf("%3d", vvpPath[i][j]);}cout << endl;}cout << "=================================" << endl;
void TestFloydWarShall(){const char* str = "12345";Graph<char, int, INT_MAX, true> g(str, strlen(str));g.AddEdge('1', '2', 3);g.AddEdge('1', '3', 8);g.AddEdge('1', '5', -4);g.AddEdge('2', '4', 1);g.AddEdge('2', '5', 7);g.AddEdge('3', '2', 4);g.AddEdge('4', '1', 2);g.AddEdge('4', '3', -5);g.AddEdge('5', '4', 6);vector<vector<int>> vvDist;vector<vector<int>> vvParentPath;g.FloydWarshall(vvDist, vvParentPath);}

还是使用上面的图解中的图

运行结果

为了方便对比

 ∞等价于*    NIL等价于-1

另外pPath数组例子中给的直接就是结点本身(他这里的顶点就是1 2 3 4 5),我们用的是顶点映射的下标,所以大家看到比他的值小1

接着我们打印一下任意两个顶点之间的最短路径

// 打印任意两点之间的最短路径for (size_t i = 0; i < strlen(str); ++i){g.PrintShortPath(str[i], vvDist[i], vvParentPath[i]);cout << endl;}

五、源码

void FloydWarshall(vector<vector<W>>& vvDist, vector<vector<int>>& vvpPath){size_t n = _vertexs.size();vvDist.resize(n);vvpPath.resize(n);// 初始化权值和路径矩阵for (size_t i = 0; i < n; ++i){vvDist[i].resize(n, MAX_W);vvpPath[i].resize(n, -1);}// 直接相连的边更新一下for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){if (_matrix[i][j] != MAX_W){vvDist[i][j] = _matrix[i][j];vvpPath[i][j] = i;}if (i == j){vvDist[i][j] = W();}}}// abcdef  a {} f ||  b {} c// 最短路径的更新i-> {其他顶点} ->jfor (size_t k = 0; k < n; ++k){for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){// k 作为的中间点尝试去更新i->j的路径if (vvDist[i][k] != MAX_W && vvDist[k][j] != MAX_W&& vvDist[i][k] + vvDist[k][j] < vvDist[i][j]){vvDist[i][j] = vvDist[i][k] + vvDist[k][j];// 找跟j相连的上一个邻接顶点// 如果k->j 直接相连,上一个点就k,vvpPath[k][j]存就是k// 如果k->j 没有直接相连,k->...->x->j,vvpPath[k][j]存就是xvvpPath[i][j] = vvpPath[k][j];}}}// 打印权值和路径矩阵观察数据for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){if (vvDist[i][j] == MAX_W){//cout << "*" << " ";printf("%3c", '*');}else{//cout << vvDist[i][j] << " ";printf("%3d", vvDist[i][j]);}}cout << endl;}cout << endl;for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){//cout << vvParentPath[i][j] << " ";printf("%3d", vvpPath[i][j]);}cout << endl;}cout << "=================================" << endl;}	}

感谢阅读!

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

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

相关文章

解决微软应用商店 (Microsoft store) 打不开,无网络连接的问题!

很多小伙伴都会遇见微软应用商店 (Microsoft store)打开后出现无网络的问题&#xff0c;一般出现这种问题基本都是因为你的电脑安装了某些银行的网银工具&#xff0c;因为网银工具为了安全会关闭Internet 选项中的最新版本的TLS协议&#xff0c;而微软商店又需要最新的TLS协议才…

Android—服务+通知=>前台服务

文章目录1、Android服务1.1、定义1.2、基本用法1.2.1、定义一个服务1.2.2、服务注册1.2.3、启动和停止服务1.2.4、活动和服务进行通信1.3、带绑定的服务示例1.3.1、定义服务类1.3.2、客户端&#xff08;Activity&#xff09;绑定与交互​1.3.3、AndroidManifest.xml 注册​1.3.…

从基础功能到自主决策, Agent 开发进阶路怎么走

Agent 开发进阶路线大纲基础功能实现核心模块构建环境感知&#xff1a;传感器数据处理&#xff08;视觉、语音、文本等输入&#xff09;基础动作控制&#xff1a;API调用、硬件驱动、简单反馈机制状态管理&#xff1a;有限状态机&#xff08;FSM&#xff09;或行为树&#xff0…

《动手学深度学习》读书笔记—9.6编码器-解码器架构

本文记录了自己在阅读《动手学深度学习》时的一些思考&#xff0c;仅用来作为作者本人的学习笔记&#xff0c;不存在商业用途。 正如我们在9.5机器翻译中所讨论的&#xff0c;机器翻译是序列转换模型的一个核心问题&#xff0c;其输入和输出都是长度可变的序列。为了处理这种类…

DocBench:面向大模型文档阅读系统的评估基准与数据集分析

本文由「大千AI助手」原创发布&#xff0c;专注用真话讲AI&#xff0c;回归技术本质。拒绝神话或妖魔化。搜索「大千AI助手」关注我&#xff0c;一起撕掉过度包装&#xff0c;学习真实的AI技术&#xff01; 一、数据集概述与核心目标 DocBench 是由研究团队于2024年提出的首个…

Python高级排序技术:非原生可比对象的自定义排序策略详解

引言&#xff1a;超越原生比较操作的排序挑战在Python数据处理中&#xff0c;我们经常需要处理不原生支持比较操作的对象。根据2024年《Python开发者生态系统报告》&#xff0c;在大型项目中&#xff0c;开发者平均需处理28%的自定义对象排序需求&#xff0c;这些对象包括&…

低代码系统的技术深度:超越“可视化操作”的架构与实现挑战

在很多非开发者眼中&#xff0c;低代码平台似乎只是简化流程、快速搭建页面的工具。然而&#xff0c;在真实的企业级应用中&#xff0c;低代码系统必须面对高并发请求、复杂业务规则、多角色权限、跨系统集成与持续演进等一系列工程挑战。高效交付&#xff08;Rapid Delivery&a…

【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 词云图-微博评论词云图实现

大家好&#xff0c;我是java1234_小锋老师&#xff0c;最近写了一套【NLP舆情分析】基于python微博舆情分析可视化系统(flaskpandasecharts)视频教程&#xff0c;持续更新中&#xff0c;计划月底更新完&#xff0c;感谢支持。今天讲解词云图-微博评论词云图实现 视频在线地址&…

Webpack核心技能:Webpack安装配置与模块化

一、webpack 的安装和使用1. webpack 简介webpack 是基于模块化的打包 (构建)工具&#xff0c;它把一切视为模块&#xff08;包括 JS、CSS、图片等资源文件&#xff09;。工作原理&#xff1a;以开发时态的入口模块为起点递归分析所有依赖关系经过压缩、合并等处理最终生成运行…

数据结构---二级指针(应用场景)、内核链表、栈(系统栈、实现方式)、队列(实现方式、应用)

一、二级指针的应用场景1、在被调函数中&#xff0c;想要修改主调函数中的指针变量&#xff0c;需要传递该指针变量的地址&#xff0c;形参用二级指针接收。2、指针数组的数组名是一个二级指针&#xff0c;指针数组的数组名作为参数传递时&#xff0c;可用二级指针接收。指针数…

NodeJs学习日志(1):windows安装使用node.js 安装express,suquelize,sqlite,nodemon

windows安装使用node.js 安装express&#xff0c;suquelize&#xff0c;sqlite 系统是win10&#xff0c;默认已经安装好nodejs与npm包名作用expressWeb应用框架suquelize数据库ORMsqlite数据库nodemon代码热重载安装express 添加express生成器 npm add express-generator4安装e…

Cervantes:面向渗透测试人员和红队的开源协作平台

Cervantes 是一个专为渗透测试人员和红队打造的开源协作平台。它提供了一个集中式工作区&#xff0c;用于集中管理项目、客户端、漏洞和报告。通过简化数据组织和团队协调&#xff0c;它有助于减少规划和执行渗透测试所需的时间和复杂性。 作为 OWASP 旗下的开源解决方案&…

[Python 基础课程]猜数字游戏

使用 Python 实现一个猜数字游戏&#xff0c;先随机生成一个 1 到 100 之间的一个随机整数&#xff0c;让用户猜测这个数是什么&#xff0c;每次都提示用户猜大了还是猜小了&#xff0c;如果用户猜对了&#xff0c;提示用户猜对了&#xff0c;用了多少次&#xff0c;并且之前每…

文件加密实现

一、不依赖外部库实现 使用自定义的XOR加密算法结合简单的密钥扩展。 实现说明 这个方案不依赖任何外部库&#xff0c;仅使用C标准库实现&#xff1a; 加密原理&#xff1a;采用XOR加密算法&#xff0c;这是一种简单但有效的对称加密方式&#xff0c;相同的密钥可以用于加密和解…

Unity轻量观察相机

一、脚本功能简介ObserveCamera 是一个可直接挂载到任意 GameObject 上的通用摄像机控制脚本&#xff0c;支持以下功能&#xff1a;鼠标右键控制摄像机绕自身旋转&#xff08;俯仰、水平&#xff09;鼠标左键拖拽目标对象进行平移&#xff08;局部 XY 平面移动&#xff09;鼠标…

1深度学习Pytorch-pytorch、tensor的创建、属性、设备和类型转换、数据转换、常见操作(获取元素、元素运算、形状改变、相乘、广播)

文章目录PyTorchTensor1 Tensor 的创建1.torch.tensor2.torch.Tensor3. 线性张量4. 随机张量5. 特定数值的张量2 Tensor 常见属性1 属性2 设备切换3 类型转换torch.Tensor.to(dtype)类型专用方法创建张量时直接指定类型与 NumPy 数组的类型互转4 数据转换&#xff08;浅拷贝与深…

五、Istio管理网格外部服务

因语雀与csdn markdown 格式有区别&#xff0c;请查看原文&#xff1a; https://www.yuque.com/dycloud/pss8ys 一、Egress Listener 流量策略 前面学习了 sidecar 自动注入原理、inbound Listener、outbound Listener 等概念&#xff0c;也知道了 EgressListener 的流量策略…

Ubuntu20.04 离线安装 FFmpeg 静态编译包

系统版本 Ubuntu20.04 去现场部署项目&#xff0c;发现现场的设备连接的内网&#xff0c;无法使用apt直接安装ffmpeg &#xff0c;想解决也简单&#xff0c;数据线连接手机使用共享网络&#xff0c;再使用命令sudo apt install ffmpeg安装即可&#xff0c;奈何现场百多台设备&a…

C语言高级编程技巧与最佳实践

C语言高级编程技巧与最佳实践 - 完整版 目录 宏定义与预处理技巧内存管理高级技巧函数指针与回调机制数据结构设计并发与多线程错误处理与异常机制性能优化技巧调试与测试技巧跨平台编程安全编程实践综合演示示例 宏定义与预处理技巧 1. 条件编译与平台检测 /*** 平台和编译…

cygwin+php教程(swoole扩展+redis扩展)

cygwin 1.下载cygwin安装程序 &#xff1a;在Windows上获得Linux的感觉 ​ 2. 打开安装包&#xff1a;setup-x86_64.exe 3.选择安装类型 从互联网安装首次安装下载而不安装仅下载软件包不安装从本地目录安装迁移程序时使用 4.选择安装目录 5.选择本地软件包目录&#xff…