【高阶数据结构】图的应用--最短路径算法
文章目录
- 一、最短路径
- 二、单源最短路径--Dijkstra算法
- 三、单源最短路径--Bellman-Ford算法
- 四、多源最短路径--Floyd-Warshall算法
一、最短路径
最短路径问题:从在带权有向图G中的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小。
二、单源最短路径–Dijkstra算法
单源最短路径问题:给定一个图G = ( V , E ) G=(V,E)G=(V,E),求源结点s ∈ V s∈Vs∈V到图中每个结点v ∈ V v∈Vv∈V的最短路径。Dijkstra算法就适用于解决带权重的有向图上的单源最短路径问题,同时算法要求图中所有边的权重非负。一般在求解最短路径的时候都是已知一个起点和一个终点,所以使用Dijkstra算法求解过后也就得到了所需起点到终点的最短路径。
针对一个带权有向图G,将所有结点分为两组S和Q,S是已经确定最短路径的结点集合,在初始时为空(初始时就可以将源节点s放入,毕竟源节点到自己的代价是0),Q 为其余未确定最短路径的结点集合,每次从Q 中找出一个起点到该结点代价最小的结点u ,将u 从Q 中移出,并放入S 中,对u 的每一个相邻结点v 进行松弛操作。松弛即对每一个相邻结点v ,判断源节点s到结点u 的代价与u 到v 的代价之和是否比原来s 到v 的代价更小,若代价比原来小则要将s 到v 的代价更新为s 到u 与u 到v 的代价之和,否则维持原样。如此一直循环直至集合Q 为空,即所有节点都已经查找过一遍并确定了最短路径,至于一些起点到达不了的结点在算法循环后其代价仍为初始设定的值,不发生变化。Dijkstra算法每次都是选择V-S中最小的路径节点来进行更新,并加入S中,所以该算法使用的是贪心策略。
Dijkstra算法存在的问题是不支持图中带负权路径,如果带有负权路径,则可能会找不到一些路径的最短路径。
代码实现:
// 临接矩阵
namespace Matrix
{template <class V, class W, W MAX_W = INT_MAX, bool Direction = false>class Graph{typedef Graph<V, W, MAX_W, Direction> Self;private:std::vector<V> _vertexs; // 顶点集合std::map<V, size_t> _vIndexMap; // 顶点的下标映射关系std::vector<std::vector<W>> _matrix; // 存储边集合的矩阵void PrintShortPath(const V &src, const std::vector<W> &dist, const std::vector<int> &pPath){size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();for (size_t i = 0; i < n; ++i){if (i != srci){// 找出i顶点的路径std::vector<int> path;size_t parenti = i;while (parenti != srci){path.push_back(parenti);parenti = pPath[parenti];}path.push_back(srci);reverse(path.begin(), path.end());for (auto index : path){std::cout << _vertexs[index] << "->";}std::cout << "权值和:" << dist[i] << std::endl;}}}// 顶点个数是N -> 时间复杂度:O(N^2)空间复杂度:O(N)void Dijkstra(const V &src, std::vector<W> &dist, std::vector<int> &pPath){size_t srci = GetVertexIndex(src);int n = _vertexs.size();dist.resize(n, MAX_W);pPath.resize(n, -1);dist[srci] = 0;pPath[srci] = srci;// 已经确定最短路径的顶点集合std::vector<bool> S(n, false);// n个节点每个节点都要作为起点for (int i = 0; i < n; i++){// 选最短路径顶点且不在S更新其他路径int u = 0;W min = MAX_W;for (int j = 0; j < n; j++){if (S[i] == false && dist[i] < min){u = i;min = dist[i];}}S[u] = true;// 松弛更新u连接顶点v srci->u + u->v < srci->v 更新for (int v = 0; v < n; v++){if (S[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 TestGraphDijkstra()
{const char *str = "syztx";Graph<char, int, INT_MAX, true> g(str, strlen(str));g.AddEdge('s', 't', 10);g.AddEdge('s', 'y', 5);g.AddEdge('y', 't', 3);g.AddEdge('y', 'x', 9);g.AddEdge('y', 'z', 2);g.AddEdge('z', 's', 7);g.AddEdge('z', 'x', 6);g.AddEdge('t', 'y', 2);g.AddEdge('t', 'x', 1);g.AddEdge('x', 'z', 4);std::vector<int> dist;std::vector<int> parentPath;g.Dijkstra('s', dist, parentPath);g.PrintShortPath('s', dist, parentPath);// // 图中带有负权路径时,贪心策略则失效了。// // 测试结果可以看到s->t->y之间的最短路径没更新出来// const char *str = "sytx";// Graph<char, int, INT_MAX, true> g(str, strlen(str));// g.AddEdge('s', 't', 10);// g.AddEdge('s', 'y', 5);// g.AddEdge('t', 'y', -7);// g.AddEdge('y', 'x', 3);// std::vector<int> dist;// std::vector<int> parentPath;// g.Dijkstra('s', dist, parentPath);// g.PrintShortPath('s', dist, parentPath);
}
三、单源最短路径–Bellman-Ford算法
Dijkstra算法只能用来解决正权图的单源最短路径问题,但有些题目会出现负权图。这时这个算法就不能帮助我们解决问题了,而bellman—ford算法可以解决负权图的单源最短路径问题。它的优点是可以解决有负权边的单源最短路径问题,而且可以用来判断是否有负权回路。它也有明显的缺点,它的时间复杂度 O(NE) (N是点数,E是边数)普遍是要高于Dijkstra算法O(N²)的。像这里如果我们使用邻接矩阵实现,那么遍历所有边的数量的时间复杂度就是O(N^3),这里也可以看出来Bellman-Ford就是一种暴力求解更新。
代码实现:
// 临接矩阵
namespace Matrix
{template <class V, class W, W MAX_W = INT_MAX, bool Direction = false>class Graph{typedef Graph<V, W, MAX_W, Direction> Self;private:std::vector<V> _vertexs; // 顶点集合std::map<V, size_t> _vIndexMap; // 顶点的下标映射关系std::vector<std::vector<W>> _matrix; // 存储边集合的矩阵void PrintShortPath(const V &src, const std::vector<W> &dist, const std::vector<int> &pPath){size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();for (size_t i = 0; i < n; ++i){if (i != srci){// 找出i顶点的路径std::vector<int> path;size_t parenti = i;while (parenti != srci){path.push_back(parenti);parenti = pPath[parenti];}path.push_back(srci);reverse(path.begin(), path.end());for (auto index : path){std::cout << _vertexs[index] << "->";}std::cout << "权值和:" << dist[i] << std::endl;}}}// 时间复杂度:O(N^3) 空间复杂度:O(N)bool BellmanFord(const V &src, std::vector<W> &dist, std::vector<int> &pPath){size_t n = _vertexs.size();size_t srci = GetVertexIndex(src);// vector<W> dist,记录srci-其他顶点最短路径权值数组dist.resize(n, MAX_W);// vector<int> pPath 记录srci-其他顶点最短路径父顶点数组pPath.resize(n, -1);// 先更新srci->srci为缺省值dist[srci] = W();// 总体最多更新n轮for (int k = 0; k < n; k++){bool update = false;std::cout << "更新第:" << k << "轮" << std::endl;for (int i = 0; i < n; i++){for (int j = 0; j < n; j++){// srci -> i i -> jif (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j]){update = true;std::cout << _vertexs[i] << "->" << _vertexs[j] << ":" << _matrix[i][j] << std::endl;dist[j] = dist[i] + _matrix[i][j];pPath[j] = i;}}}// 如果这个轮次中没有更新出更短路径,那么后续轮次就不需要再走了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;}}}return true;}};
}
测试代码:
void TestGraphBellmanFord()
{// const char *str = "syztx";// Graph<char, int, INT_MAX, true> g(str, strlen(str));// g.AddEdge('s', 't', 6);// g.AddEdge('s', 'y', 7);// g.AddEdge('y', 'z', 9);// g.AddEdge('y', 'x', -3);// g.AddEdge('z', 's', 2);// g.AddEdge('z', 'x', 7);// g.AddEdge('t', 'x', 5);// g.AddEdge('t', 'y', 8);// g.AddEdge('t', 'z', -4);// g.AddEdge('x', 't', -2);// std::vector<int> dist;// std::vector<int> parentPath;// g.BellmanFord('s', dist, parentPath);// g.PrintShortPath('s', dist, parentPath);// 微调图结构,带有负权回路的测试const char *str = "syztx";Graph<char, int, INT_MAX, true> g(str, strlen(str));g.AddEdge('s', 't', 6);g.AddEdge('s', 'y', 7);g.AddEdge('y', 'x', -3);g.AddEdge('y', 'z', 9);g.AddEdge('y', 'x', -3);g.AddEdge('y', 's', 1); // 新增g.AddEdge('z', 's', 2);g.AddEdge('z', 'x', 7);g.AddEdge('t', 'x', 5);g.AddEdge('t', 'y', -8); // 更改g.AddEdge('t', 'z', -4);g.AddEdge('x', 't', -2);std::vector<int> dist;std::vector<int> parentPath;if (g.BellmanFord('s', dist, parentPath))g.PrintShortPath('s', dist, parentPath);elsestd::cout << "带负权回路" << std::endl;
}
四、多源最短路径–Floyd-Warshall算法
Floyd-Warshall算法是解决任意两点间的最短路径的一种算法。
Floyd算法考虑的是一条最短路径的中间节点,即简单路径p={v1,v2,…,vn}上除v1和vn的任意节点。
设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}取得的一条最短路径。
即Floyd算法本质是三维动态规划,D[i][j][k]表示从点i到点j只经过0到k个点最短路径,然后建立起转移方程,然后通过空间优化,优化掉最后一维度,变成一个最短路径的迭代算法,最后即得到所以点的最短路。
代码实现:
// 临接矩阵
namespace Matrix
{template <class V, class W, W MAX_W = INT_MAX, bool Direction = false>class Graph{typedef Graph<V, W, MAX_W, Direction> Self;private:std::vector<V> _vertexs; // 顶点集合std::map<V, size_t> _vIndexMap; // 顶点的下标映射关系std::vector<std::vector<W>> _matrix; // 存储边集合的矩阵void PrintShortPath(const V &src, const std::vector<W> &dist, const std::vector<int> &pPath){size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();for (size_t i = 0; i < n; ++i){if (i != srci){// 找出i顶点的路径std::vector<int> path;size_t parenti = i;while (parenti != srci){path.push_back(parenti);parenti = pPath[parenti];}path.push_back(srci);reverse(path.begin(), path.end());for (auto index : path){std::cout << _vertexs[index] << "->";}std::cout << "权值和:" << dist[i] << std::endl;}}}void FloydWarshall(std::vector<std::vector<W>> &vvDist, std::vector<std::vector<int>> &vvpPath){size_t n = _vertexs.size();vvDist.resize(n);vvpPath.resize(n);// 初始化权值和路径矩阵for (int i = 0; i < n; i++){vvDist[i].resize(n, MAX_W);vvpPath[i].resize(n, -1);}for (int i = 0; i < n; i++){for (int 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 (int k = 0; k < n; k++){for (int i = 0; i < n; i++){for (int j = 0; j < n; j++){// i -> j i -> k + k -> 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 (int i = 0; i < n; i++){for (int j = 0; j < n; j++){if (vvDist[i][j] == MAX_W){printf("%3c", '*');}else{printf("%3d", vvDist[i][j]);}}std::cout << std::endl;}std::cout << std::endl;for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){printf("%3d", vvpPath[i][j]);}std::cout << std::endl;}std::cout << "=================================" << std::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);std::vector<std::vector<int>> vvDist;std::vector<std::vector<int>> vvParentPath;g.FloydWarshall(vvDist, vvParentPath);// 打印任意两点之间的最短路径for (size_t i = 0; i < strlen(str); ++i){g.PrintShortPath(str[i], vvDist[i], vvParentPath[i]);}
}
相关文章:

【高阶数据结构】图的应用--最短路径算法
文章目录 一、最短路径二、单源最短路径--Dijkstra算法三、单源最短路径--Bellman-Ford算法四、多源最短路径--Floyd-Warshall算法 一、最短路径 最短路径问题:从在带权有向图G中的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿…...

腾讯云函数node.js返回自动带反斜杠
云函数返回自动带反斜杠 这里建立了如下一个云函数,目的是当APP过来请求的时候响应支持的版本号: use strict; function main_ret(status,code){let ret {status: status,error: code};return JSON.stringify(ret); } exports.main_handler async (event, context) > {/…...

大模型知识学习
大模型训练过程 数据清洗 拟人化描述:知识库整理 预训练 拟人化描述:知识学习可以使用基于BERT预训练模型进行训练 指令微调 拟人化描述:实际工作技能学习实际操作:让大模型模仿具体的输入输出进行拟合,即模仿学…...

JAVA声明数组
一、声明并初始化数组 直接初始化:在声明数组的同时为其分配空间并初始化元素。 int[] numbers {1, 2, 3, 4, 5}; 动态初始化:先声明数组,再为每个元素分配初始值。 double[] decimals;decimals new double[5]; // 分配空间,但…...

VBA通过Range对象实现Excel的数据写入
前言 本节会介绍通过VBA中的Range对象,来实现Excel表格中的单元格写入、区域范围写入,当然也可以写入不同类型的数据,如数值、文本、公式,以及实现公式下拉自动填充的功能。 一、单元格输入数据 1.通过Value方法实现输入不同类型…...

记录OSPF配置,建立邻居失败的过程
1.配置完ospf后,在路由表中不出现ospf相关信息 [SW2]ospf [SW2-ospf-1]are [SW2-ospf-1]area 0 [SW2-ospf-1-area-0.0.0.0]net [SW2-ospf-1-area-0.0.0.0]network 0.0.0.0 Jul 4 2024 22:11:58-08:00 SW2 DS/4/DATASYNC_CFGCHANGE:OID 1.3.6.1.4.1.2011.5.25 .1…...

算法体系-25 第二十五节:窗口内最大值或最小值的更新结构
一 滑动窗口设计知识点 滑动窗口是什么? 滑动窗口是一种想象出来的数据结构: 滑动窗口有左边界L和有边界R 在数组或者字符串或者一个序列上,记为S,窗口就是S[L..R]这一部分 L往右滑意味着一个样本出了窗口,R往右滑意味…...

等保2.0中还有哪些针对云计算的安全要求?
等保2.0中针对云计算的安全要求概述 等保2.0是中国信息安全等级保护制度的升级版,它对云计算环境提出了一系列特定的安全要求,以确保云服务的安全性和合规性。以下是一些关键的云计算安全扩展要求: 基础设施位置:要求云计算基础…...

数组与 ArrayList 的区别是什么?
在Java中,数组和ArrayList都是非常常见的数据结构,但它们在使用场景、特点和功能上各有千秋。 理解它们的不同,对于初级Java工程师来说,是提升编程技能的一个重要环节。 下面,我将以一种简单明了的方式,对…...

华为OD机考题(HJ50 四则运算)
前言 经过前期的数据结构和算法学习,开始以OD机考题作为练习题,继续加强下熟练程度。 描述 输入一个表达式(用字符串表示),求这个表达式的值。 保证字符串中的有效字符包括[‘0’-‘9’],‘’,‘-’, ‘*’,‘/’ …...

SpringBoot实现文章点赞功能
提示:今日是2024年的6月30日,未来的你看到这篇文章,希望你依旧快乐 文章目录 前言 首先在这里前缀部分我就不做要求了,比如说登录信息什么的 数据库表格 这里实现点赞功能,主要是围绕论坛项目完成的 user_info代表用户信息表 for…...

产品经理系列1—如何实现一个电商系统
具体笔记如下,主要按获客—找货—下单—售后四个部分进行模块拆解...

论文翻译 | (DSP)展示-搜索-预测:为知识密集型自然语言处理组合检索和语言模型
摘要 检索增强式上下文学习已经成为一种强大的方法,利用冻结语言模型 (LM) 和检索模型 (RM) 来解决知识密集型任务。现有工作将这些模型结合在简单的“检索-读取”流程中,其中 RM 检索到的段落被插入到 LM 提示中。 为了充分发挥冻结 LM 和 RM 的…...

1.(vue3.x+vite)实现卷帘效果
前端技术社区总目录(订阅之前请先查看该博客) 1:效果预览 2:代码编写 <template><div style="width...

HMI 的 UI 风格成就经典
HMI 的 UI 风格成就经典...

金融(基金)行业信创国产化特点及统一身份认证解决方案
金融业在政策支持及自主驱动下,金融信创取得快速发展。从2020年开始,三期试点已扩容至5000余家,进入全面推广阶段。而基金行业信创建设与银行、证券、保险这些试点行业相比,进展较为缓慢。 基金行业信创当前面临的问题 与多家基…...

透过 Go 语言探索 Linux 网络通信的本质
大家好,我是码农先森。 前言 各种编程语言百花齐放、百家争鸣,但是 “万变不离其中”。对于网络通信而言,每一种编程语言的实现方式都不一样;但其实,调用的底层逻辑都是一样的。linux 系统底层向上提供了统一的 Sock…...

【C语言】—— 文件操作(下)
【C语言】—— 文件操作(下) 前言:五、文件的顺序读写5.1、 顺序读写函数介绍5.2、 f p u t c fputc fputc 函数5.3、 f g e t c fgetc fgetc 函数5.4、 f p u t s fputs fputs 函数5.5、 f g e t s fgets fgets 函数5.6、 f p r i n t f…...

np.argsort
函数解释 np.argsort是NumPy库中的一个函数,用于对数组进行排序并返回排序后的索引。它不会直接对数组进行排序,而是返回一个数组,这个数组中的元素是原数组中元素按升序排序后的索引。 numpy.argsort(a, axis-1, kindNone, orderNone) 参…...

ORC与Parquet列式存储的区别
ORC与Parquet列式存储 1、ORC与Parquet列式存储2、ORC与Parquet的区别 列式存储(Columnar Storage)是一种优化的数据存储方式,与传统的行式存储(Row Storage)相比,列式存储在数据压缩、查询性能、I/O效率等…...

析构函数和拷贝构造函数
文章目录 析构函数1.析构函数的定义:2.析构函数的语法:3.析构函数的特性: 拷贝构造函数1.拷贝构造函数的定义:2.拷贝构造函数的语法3.拷贝构造函数的特性(1)拷贝构造函数是构造函数的一个重载形式**(这个其实也很好理解࿰…...

sql server启动、连接 与 navicat连接sql server
一、sql server 启动 1.搜索cmd->以管理员身份运行 2.输入以下命令 net start mssqlserver 3.服务器启动成功 二、sql server连接 1.打开ssms,输入,连接 2.右键,属性 3.连接,勾选允许远程连接到此服务器 三、navicat连接sq…...

数据库测试数据准备厂商 Snaplet 宣布停止运营
上周刚获知「数据库调优厂商 OtterTune 宣布停止运营」。而今天下班前,同事又突然刷到另一家海外数据库工具商 Snaplet 也停止运营了。Snaplet 主要帮助开发团队在数据库中生成仿真度高且合规的测试数据。我们在年初还撰文介绍过它「告别手搓!Postgres 一…...

【Java09】方法(下)
1. 形参个数可变的方法 Java允许方法指定数量不确定的形参。如果在定义方法是,在最后一个形参的类型后加...,则表明该形参可以接受多个参数值。多个参数值作为数组传入: public class Varargs {public static void test(int a, String... b…...

d88888888
分析:v9999999999 vn输出n个n 先算出n的位数p 所以答案是nn*10的p次方n*10的2p次方.....n*10的(n-1)p次方 化简n*(10的0次方10的p次方10的2p次方.....10的(n-1)p次方) 后面为等比数列求和 …...

【MySQL备份】mysqldump基础篇
目录 1.简介 2.基本用途 3.命令格式 3.1常用选项 3.2常用命令 4.备份脚本 5.定时执行备份脚本 1.简介 mysqldump 是 MySQL 数据库管理系统的命令行实用程序,用于创建数据库的逻辑备份。它能够导出数据库的结构(如表结构、视图、触发器等…...

C# Halcon目标检测算法
在Halcon中进行目标检测可以使用传统的计算机视觉方法,也可以使用深度学习的方法。Halcon提供了丰富的函数库来处理这些任务,而在C#中使用Halcon,你需要通过Halcon .NET接口。 以下是使用Halcon进行目标检测的一般步骤,这里我将给…...

7.4总结
今天写了几道题目 最近,一年级学生马克西姆学习了科拉兹猜想,但他在讲课时没有太注意,所以他认为猜想中提到了以下过程: 有一个变量 $$$x$$$ 和一个常数 $$$y$$$ 。下面的操作要执行 $$$k$$$ 次: - 将 $$$x$$$ 增加…...

知识图谱查询语言的表示
文章目录 SPARQL知识图谱查询基本构成常见的SPARQL查询算子语义Markup表示语言SPARQL知识图谱查询基本构成 RDF 支持类似数据库的查询语言,叫作SPARQL,它提供了查询RDF 数据的标准语法、处理SPARQL查询的规则以及结果返回形式。 变量,RDF中的资源,以“?”或者“$”指示;…...

重生之我要学后端100--计算机网络部分概念(持续更新)
TCP/IP、DNS、负载均衡器等等 前言一、TCP/IP(传输控制协议/互联网协议)二、DNS(域名系统)三、负载均衡器其他网络概念 前言 了解网络基础知识对于后端开发者至关重要,因为这些知识有助于理解应用程序是如何在更广阔的…...