【高阶数据结构】图--最短路径问题

图--最短路径问题

  • 一、单源最短路径--Dijkstra算法
    • 1、简介
    • 2、解析
    • 3、代码
    • 4、测试用例
    • 5、打印最小路径代码和测试
    • 6、缺陷:不能使用负路径
  • 二、单源最短路径--Bellman-Ford算法
    • 1、简介
    • 2、解析
      • (1)详情
        • i、负权问题:一个点只跑一趟找最短路径(问题大大的)
        • ii、解决负权问题:每个点在更新完最短路径后继续暴力再继续遍历更新最短路径
      • (2)优化
        • i、优化策略1:使用bool标记位跳出循环,后面循环不用跑
        • ii、优化策略2:使用SPFA算法优化(队列)(未做出)
    • 3、代码
    • 4、测试用例及测试结果
    • 5、负权回路问题
      • (1)问题描述和解析
      • (2)出现负权问题的代码用例及测试结果
  • 三、多源最短路径--Floyd-Warshall算法
    • (1)简介
    • (2)详细解析(用图)
    • (3)代码
    • (4)运行用例及测试结果


一、单源最短路径–Dijkstra算法

1、简介

单源最短路径问题:给定一个图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算法存在的问题是不支持图中带负权路径,如果带有负权路径,则可能会找不到一些路径的最短路径。

2、解析

在这里插入图片描述

3、代码

		// Dijkstrala算法// dist是用来存放最小值的表的// pPath是用来存放父亲下标的void Dijkstra(const V& src, std::vector<W>& dist, std::vector<int>& pPath){size_t srci = GetIndex(src);size_t n = _vertex.size();// 初始化dist表和pPath表dist.resize(n, MAX_W);pPath.resize(n, -1);dist[srci] = 0; // 当前的第一个位置的距离为0pPath[srci] = srci; // 这个可有可无// 来一个S为存放加入到集合中的已经确定了的点std::vector<bool> S(n, false);for (size_t i = 0; i < n; i++) // 遍历n次--n个顶点{// 选最短路径顶点且不在S更新其他路径size_t u = 0;size_t min = MAX_W;// 遍历输入值的表,选最小值for (size_t i = 0; i < n; i++){if (S[i] == false && dist[i] < min) // false是没有进行访问过{u = i; // 更新一下当前的最短路径的顶点min = dist[i]; // 更新点的最小值}}S[u] = true; // 将刚好访问的这个顶点设置为true已经被访问过的地方// 松弛算法,更新u连接顶点v  srci->u + u->v <  srci->v  更新for (size_t v = 0; v < n; v++){if (S[v] == false && dist[u] + _matrix[u][v] < dist[v] && _matrix[u][v] != MAX_W){dist[v] = dist[u] + _matrix[u][v]; // 更新一下v的点的值pPath[v] = u; // 更新父下标的点的值}}}}

4、测试用例

	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.PrinrtShotPath('s', dist, parentPath);}

5、打印最小路径代码和测试

		void PrinrtShotPath(const V& src, const std::vector<W>& dist, const std::vector<int>& parentPath){size_t N = _vertex.size();size_t srci = GetIndex(src);for (size_t i = 0; i < N; ++i){if (i == srci)continue;std::vector<int> path;int parenti = i;while (parenti != srci){path.push_back(parenti); // 先把自己存进去parenti = parentPath[parenti]; // 往父亲去跳}path.push_back(srci); // 最后存初始位置reverse(path.begin(), path.end()); // 逆置一下for (auto pos : path){std::cout << _vertex[pos] << "->";}std::cout << dist[i] << std::endl;}}

在这里插入图片描述

6、缺陷:不能使用负路径

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.PrinrtShotPath('s', dist, parentPath);

在这里插入图片描述

二、单源最短路径–Bellman-Ford算法

1、简介

Dijkstra算法只能用来解决正权图的单源最短路径问题,但有些题目会出现负权图。这时这个算法就不能帮助我们解决问题了,而bellman—ford算法可以解决负权图的单源最短路径问题。它的优点是可以解决有负权边的单源最短路径问题,而且可以用来判断是否有负权回路。它也有明显的缺点,它的时间复杂度 O(N*E) (N是点数,E是边数)普遍是要高于Dijkstra算法O(N²)的。像这里如果我们使用邻接矩阵实现,那么遍历所有边的数量的时间复杂度就是O(N^3),这里也可以看出来Bellman-Ford就是一种暴力求解更新。

2、解析

(1)详情

i、负权问题:一个点只跑一趟找最短路径(问题大大的)

在这里插入图片描述

在这里插入图片描述

ii、解决负权问题:每个点在更新完最短路径后继续暴力再继续遍历更新最短路径

原因还是因为负权路径的问题,当我们更新完一趟的最短路径后发现其父亲节点是个负数,刚好通过这条路,而我们前面更新的最短路径不是从这条路走的,那么就需要继续更新前面这个父亲结点的最短路径为负数,使得这个最短路径更新成更小的值,那么一切就说通了,我们就需要继续更新呗,那么就继续更新n次,每个点继续遍历n次。

// 进行n轮循环,原因是因为防止因为某一轮循环后值会产生更改导致的结果不正确// 所以每次进行点的询问松弛,使得后续的路径更新成更短路径for (int k = 0; k < n; k++){std::cout << "第" << k << "轮次" << std::endl;}

我们跟着最终结果来看:
在这里插入图片描述

(2)优化

i、优化策略1:使用bool标记位跳出循环,后面循环不用跑

因为第一个轮次是将所有的路都跑一遍(初始跑一遍),第二个轮次还是从头到尾的跑一遍,这回轮次是找负数重新更新一下最短路径,而假如说路径的负数并不多的时候,两个轮次就能全部跑完了,并不需要后面冗余的n-3个轮次,所以我们给个标志位,从第i个轮次发现根本不会更改路劲的情况下我们直接退出,不需要执行下一个轮回的操作。
在这里插入图片描述

在这里插入图片描述

ii、优化策略2:使用SPFA算法优化(队列)(未做出)

在这里插入图片描述

革命尚未成功,同志仍需努力…

3、代码

		// 贝尔曼福特算法// 时间复杂度--O(N^3) 空间复杂度--O(N)bool BellmanFord(const V& src, std::vector<W>& dist, std::vector<int>& pPath){size_t n = _vertex.size();size_t srci = GetIndex(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; // 这个判断标志是提高效率的,只需要进行相对应的轮次更新松弛即可// 第一轮都更新,第二轮往后不一定了for (int i = 0; i < n; i++){for (int j = 0; j < n; j++){// srci->i i->jif (dist[i] + _matrix[i][j] < dist[j] && _matrix[i][j] != MAX_W){// 更新最小值+更新pPathdist[j] = dist[i] + _matrix[i][j];pPath[j] = i;update = true;}}}if (update == false){break;}}// 到这里判断一下是否有负权回路,因为加入说是三个形成一个带有负值回路,那么每一轮都会将值更新// 这就是负权回路问题,遇到这个负权回路问题也解决不了,那么就返回false即可for (int i = 0; i < n; i++){for (int j = 0; j < n; j++){// srci->i i->jif (dist[i] + _matrix[i][j] < dist[j] && _matrix[i][j] != MAX_W){return false;}}}return true;}

4、测试用例及测试结果

	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;if (g.BellmanFord('s', dist, parentPath)){g.PrinrtShotPath('s', dist, parentPath);}else{std::cout << "存在负权回路" << std::endl;}}

在这里插入图片描述

在这里插入图片描述

5、负权回路问题

(1)问题描述和解析

我们出现负权回路的问题,大概率是出现一个环,这个环中刚好有一个或多个边的权值是负数,如下图所示:在这里插入图片描述
那么我们想一想是否这种情况能不能避免或者是有什么算法避免?实际上一旦遇到这个问题就完蛋了,没有任何算法能够规避,直接说出现回路问题,不管了!

(2)出现负权问题的代码用例及测试结果

// 微调图结构,带有负权回路的测试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.PrinrtShotPath('s', dist, parentPath);}else{std::cout << "存在负权回路" << std::endl;}

在这里插入图片描述

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

(1)简介

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}取得的一条最短路径。

(2)详细解析(用图)

在这里插入图片描述

除了时间复杂度太高几乎没别的缺点了,它允许负权路径的存在。

(3)代码

		// FloydWarshall算法--多源最短路径的表示// vvDist是二维用来存放最短路径的表格// vvpPath是二维用来存放父路径的void FloydWarshall(std::vector<std::vector<W>>& vvDist, std::vector<std::vector<int>>& vvpPath){size_t n = _vertex.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) // 当i->j点是有路的时候{vvDist[i][j] = _matrix[i][j];vvpPath[i][j] = i; // 刚好是j的前一个路径就是i}if (i == j) // 对角线{vvDist[i][j] = W(); // 缺省的默认值}}}// 算法核心:1、中间有k的时候:i->{n个数(包含k)}->j   dist[i][k] + dist[k][j] 与 dist[i][j]比较// 2、中间没有k的时候,那么就是i->j,也就是和上面的情况一样了,压根都不需要管了for (size_t k = 0; k < n/*这里k的值是n的原因在于ij也可以都包进来的*/; k++){for (size_t i = 0; i < n; i++){for (size_t j = 0; j < n; j++){// i到k有路径 k到j有路径if (vvDist[i][k] + vvDist[k][j] < vvDist[i][j] && vvDist[i][k] != MAX_W && vvDist[k][j] != MAX_W){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]; // 动态规划--j的前面一个元素k咯}}}}// 打印权值和路径矩阵观察数据for (size_t i = 0; i < n; ++i){for (size_t 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;}

(4)运行用例及测试结果

	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.PrinrtShotPath(str[i], vvDist[i], vvParentPath[i]);std::cout << std::endl;}}

在这里插入图片描述

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

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

相关文章

CST电磁仿真软件远场变更和结果相关【从入门到精通】

1、使用阵列系数计算阵列远场结果 对单一天线进行 仿真分析后&#xff0c;查看反映阵列系数的远场结果&#xff01; Navigation Tree > Farfields > Selection > Farfield Plot > Array Factor 下面介绍一下&#xff0c;对单一天线进行仿真后&#xff0c;轻松计…

股指期货基差衡量的是什么?

在股指期货市场中&#xff0c;基差、升水和贴水是三个关键的术语&#xff0c;这些基差衡量的是现货市场的价格与期货市场的价格之间的差异。 一、基差&#xff1a;现货与期货的价差 1. 定义&#xff1a;基差是指现货价格与相应期货合约价格之间的差额。计算方式是现货价格减去…

「Python绘图」绘制奥运五环

python 绘制奥运五环 一、预期结果 二、核心代码 import turtle print("开始绘制奥运五环")# 创建Turtle对象 pen turtle.Turtle() pen.shape("turtle") pen.pensize(8)print("绘制蓝色圆") pen.up() pen.goto(50-170,0) pen.down() pen.color…

【千帆AppBuidler】零代码构建AI人工智能应用,全网都在喊话歌手谁能应战,一键AI制作歌手信息查询应用

欢迎来到《小5讲堂》 这是《千帆平台》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 背景创建应用平台地址随机生成快速创建应用头像应用名称应用描述…

【全开源】JAVA红娘婚恋相亲交友系统源码支持微信小程序+微信公众号+H5+APP

红娘婚恋相亲交友系统&#xff1a;遇见你的命中注定 在快节奏的现代生活中&#xff0c;许多单身男女都在寻找一个平台&#xff0c;希望能遇见那个能与自己携手共度一生的伴侣。红娘婚恋相亲交友系统正是为了满足这一需求而诞生的&#xff0c;它旨在为广大单身男女提供一个安全…

20240514基于深度学习的弹性超材料色散关系预测与结构逆设计

论文&#xff1a;Dispersion relation prediction and structure inverse design of elastic metamaterials via deep learning DOI&#xff1a;https://doi.org/10.1016/j.mtphys.2022.100616 1、摘要 精心设计的超材料结构给予前所未有的性能&#xff0c;保证了各种各样的具…

Java(二)——方法与数组

文章目录 方法与数组方法方法的定义方法的执行实参与形参方法重载方法签名 数组创建与初始化数组的类型数组应用转字符串排序查找&#xff08;二分&#xff09;填充拷贝判等 二维数组创建及初始化遍历本质和内存分布不规则二维数组 方法与数组 方法 什么是方法&#xff1f; …

uniapp 开启阿里云服务并开启unipush消息推送

本篇只是为了记录初次使用unipush服务的一些步骤 一、开启uniCloud云开发环境&#xff0c;新建阿里云服务 1、项目右键选择创建uniCload云开发环境&#xff0c;选择阿里云 2、在项目里面新创建的uniCloud文件夹右键选择关联云服务空间或项目&#xff0c;并点击新建 3、在服务…

JavaWeb--18 tlias-web-management 登录认证

登录认证 1 登录功能功能开发 2 登录校验2.1 问题分析2.2 会话技术CookieSession令牌技术 2.3 JWT令牌介绍生成和校验登录下发令牌 2.4 过滤器Filter拦截路径过滤器链 登录校验-Filter 2.5 拦截器InterceptorInterceptor详解执行流程 登录校验- Interceptor 3 异常处理3.1 当前…

无人售货奶柜:掘金新零售蓝海,

无人售货奶柜&#xff1a;掘金新零售蓝海&#xff0c; 在日新月异的商业浪潮中&#xff0c;无人奶柜犹如一股清新的创业飓风&#xff0c;正以不可阻挡之势吸引着众多创业者的目光。这股新兴力量以其独到之处和庞大的市场蓝海&#xff0c;预示着一场关于健康、便捷消费方式的深…

全域运营平台是什么?优缺点有哪些?

当下&#xff0c;全域运营赛道逐渐兴盛&#xff0c;全域运营服务商的数量也开始呈现爆发趋势。在此背景下&#xff0c;很多人都对某些品牌的全域运营平台优缺点产生了浓厚的兴趣。由于小编只使用过微火全域运营平台&#xff0c;因此&#xff0c;本期会着重分析微火运营平台的优…

【工具篇】-什么是.NET

“.NET"&#xff1a;.NET Core是由Microsoft开发&#xff0c;目前在.NET Foundation(一个非营利的开源组织)下进行管理。.NET Core是用C#和C编写的&#xff0c;并采用MIT协议作为开源协议。 简单来说&#xff1a;就是开发框架。 .NET 又称 .NET 平台或 .NET 框架&#xf…