图详解第四篇:单源最短路径--Dijkstra算法

文章目录

  • 1. 最短路径问题
  • 2. 单源最短路径--Dijkstra算法
    • 算法思想
    • 图解
    • 如何存储路径及其权值
    • 代码实现
    • 调式观察
    • 打印最短路径
    • Dijkstra算法的缺陷
  • 3. 源码

1. 最短路径问题

最短路径问题:

从带权有向图(求最短路径通常是有向图)G中的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小。

那下面我们就要来学习几个求最短路径的算法

2. 单源最短路径–Dijkstra算法

这篇文章我们先来学习第一个求单源最短路径的算法——迪杰斯特拉算法(Dijkstra),是由荷兰计算机科学家狄克斯特拉于1959年提出的,然后后面我们还会学到求多源最短路径的算法。

所以这里先给大家介绍一下什么是单源最短路径,什么是多源最短路径:

单源最短路径指的是从一个源节点出发,计算到其他所有节点的最短路径。也就是说,在单源最短路径问题中,只需要确定一个起点,然后计算该起点到图中所有其他节点的最短距离。
多源最短路径则是在图中计算任意两个节点之间的最短路径。换言之,需要求解所有可能的起点和终点之间的最短路径。

那下面我们就来学习一下第一个求单源最短路径的算法——Dijkstra算法

算法思想

首先我们可以先从概念上了解一下Dijkstra算法的思想:

单源最短路径问题:给定一个图G = ( V , E ) ,求源结点s ∈ V 到图中每个结点v ∈ V的最短路径。Dijkstra算法就适用于解决带权重的有向图上的单源最短路径问题,同时算法要求图中所有边的权重非负。一般在求解最短路径的时候都是已知一个起点和一个终点,所以使用Dijkstra算法求解过后也就得到了所需起点到终点的最短路径。
针对一个带权有向图G,将所有结点分为两组S和Q,S是已经确定最短路径的结点集合,在初始时为空(初始时就可以将源节点s放入,毕竟源节点到自己的代价是0),Q 为其余未确定最短路径的结点集合,每次从Q 中找出一个从起点到该结点代价最小的结点u ,将u 从Q 中移出,并放入S 中,对u 的每一个相邻结点v (且v不在S中)进行松弛操作。松弛即对每一个相邻结点v ,判断源节点s到结点u 的代价与u 到v 的代价之和是否比原来s 到v 的代价更小,若代价比原来小则要将s 到v 的代价更新为s 到u 与u 到v 的代价之和,否则维持原样。如此一直循环直至集合Q 为空,即所有节点都已经查找过一遍并确定了最短路径,至于一些起点到达不了的结点在算法循环后其代价仍为初始设定的值,不发生变化。
Dijkstra算法每次都是选择V-S中最小的路径节点来进行更新,并加入S中,所以该算法使用的是贪心策略。

Dijkstra算法存在的问题是不支持图中带负权路径,如果带有负权路径,则可能会找不到一些路径的最短路径,这个我们后面也会给大家演示。

图解

那只看上面的概念的话,大家可能还不是特别理解,那下面我们来画图带大家分析一下

首先,我们可以先来看一下算法导论上给出的图解:

在这里插入图片描述
大家可以自己先看一下

然后,我来带大家走一遍这个过程:

其实就按照上面的思想一步步走就行了。
按照上面说的,将所有结点分为两组S和Q,S是已经确定最短路径的结点集合,Q 为其余未确定最短路径的结点集合。
那起始的时候,可以认为S是空的,所有结点都在Q里面。
然后这里选择的起点是s
在这里插入图片描述
每次从Q 中找出一个从起点到该结点代价最小的结点u,那第一次这个结点u就是s,可以认为s到s的距离是0(图中每个结点里面的值就表示当前从起点到自己的最短路径,还没更新的路径用标识),那把s结点放到S集合里面,Q中删去s;
然后对s 的每一个相邻结点v 进行松弛操作(其实去更新起点到它相邻点的距离),s到它相邻的两个结点距离s-t为10,s-y为5,都比原来从起点到它们的距离小,所以更新
在这里插入图片描述
然后再从Q里面找一个到起点路径最短的点,那这次找到的是y(此时s-y为5是最小的),把y从Q中移除,放入S里面;
然后对y进行松弛操作
在这里插入图片描述
y相邻的几个顶点到y的距离+y到起点s的距离都比之前起点到它们的距离短,所以都更新
接着继续从Q中选一个到起点距离最短的是z,z从Q中移出,放入S;
接着对x进行松弛操作,更新相应的距离
在这里插入图片描述
接着继续从Q中选一个到起点距离最短的是t,t从Q中移出,放入S;
接着对t进行松弛操作,更新相应的距离
在这里插入图片描述
再接着继续从Q中选一个到起点距离最短的是x,x从Q中移出,放入S;
接着再对x进行松弛操作
在这里插入图片描述
至此,集合Q 为空(起始Q是满的,所以n个结点的话,其实就选了n次去更新),即所有节点都已经查找过一遍并确定了最短路径
算法执行结束!

如何存储路径及其权值

相信算法现在大家已经理解了,但是还有一些问题需要我们解决:

既然我们是要求最短路径,那肯定得把相关的信息存储起来啊,上面图中直接把每个顶点对应最短路径的权值直接写到了结点里面,而且每条路径是怎么走的,经过了哪些顶点,我们也很容易看出来。

可是后面我们要写代码,那在写代码的时候我们如何把这些信息也存储起来呢?

🆗,是这样处理的:
因为每个顶点不是都映射一个下标嘛,所以我们就可以搞一个数组,每个下标映射的顶点是谁,这个位置就存储起点到这个顶点的最短路径。
那最开始就是这样的:
在这里插入图片描述
然后后面我们每次更新最短路径的时候修改里面的权值就行了
那上面存的是最短路径的权值,那路径又要如何存储呢?
一条路径可能会经过多个顶点啊。
🆗,那这里呢还是用一个一维数组就可以搞定:
怎么做呢?
很简单,每个顶点映射的位置存储路径上在它前面的那个顶点映射的下标,如果把路径看成一棵树的话,就是存储它的父结点的下标
比如最开始就可以这样存
在这里插入图片描述
首先s自己就是起点,可以认为最短路径就是s->s,所以它存自己的下标,然后剩下的顶点都还没有更新最短路径,起始存一个-1
接着每走一步就去更新数组就行了(存路径上它前面的那个结点(可以认为是它的父结点)的下标,类似并查集那里用的双亲表示法存储),那到最后的时候,就是这样的
在这里插入图片描述
那这样存储路径的话我们想要获取一个顶点的最短路径的时候,就从这个顶点开始顺序它的父亲(路径中的上一个结点)往上找就行了,找到起点停止,就是一条完整的路径(类似并查集里面的findRoot顺着父结点向上找根)。

代码实现

那下面我们就来实现一下代码:

首先需要给一个起点,然后两个vector存储最短路径及对应的路径权值在这里插入图片描述
然后,按照我们上面分析的思路走就行了
在这里插入图片描述
注释写的比较清楚,相信大家应该很容易可以看懂,说一点就是我们现在用的是邻接矩阵结构,所有查找u相邻的结点是去邻接矩阵_matrix里面找,如果下标[u][v]的位置对应的权值不是MAX_W,那它们就相连的,v就是u的一个相邻顶点,然后再判断如果源节点s到结点u 的代价与u 到v 的代价之和(其实就是距离嘛)是否比原来s 到v 的代价更小,若代价比原来小则要将s 到v 的代价更新为s 到u 与u 到v 的代价之和(更新距离)

调式观察

那这就实现好了,我们可以先通过调式观察一下:

在这里插入图片描述
我这里已经写好了一个测试用例(最后会给大家分享源码),这个图就是我们上面讲解思想的时候对应的那个图
我们来调式观察一下
在这里插入图片描述
对比一下我们之前分析的
在这里插入图片描述
没有问题,一模一样

打印最短路径

那下面呢我们可以写一个大于路径的函数,把最终得到的起点到各顶点的最短路径以及权值都打印出来看一下,和上面图上的是否一样:

在这里插入图片描述
那我们拿到这两个数组就可以去打印

但是这里打印的时候有一个问题:

按我们上面说的,找路径的时候通过pPath数组顺着结点的父亲或者说它的路径上的前一个结点,往上找就行了,找到起点停止。
但是!
这样找出来的路径是不是反的啊,因为我们最后找到的是起点,而正常情况应该是从起点开始嘛。
所以我们要处理一下,也很好搞:
我们可以搞一个vector把路径上的结点保存下来,然后逆置一下,再去打印就行了

来实现一下:

在这里插入图片描述

然后我们来打印一下看看:

在这里插入图片描述
在这里插入图片描述
大家可以对照一下,没有问题

Dijkstra算法的缺陷

但是呢,Dijkstra算法是有一些缺陷的,对于带有负权值的边的图,Dijkstra算法是搞不定的!

这里呢也准备了一个测试用例,我们可以来看一下:

在这里插入图片描述
首先它对应的一个图应该是这样的:
在这里插入图片描述
那我们现在对这个图执行Dijkstra算法并打印出来看一下:
在这里插入图片描述
是这样一个结果
在这里插入图片描述
但是我们会发现如果按照这个图有负权值的话,其实有一条最短路径其实没有更新出来,s->t->y应该是3(10+(-7)=3)
也就是说3应该是起点s到y的最短距离,s->t->y是最短路径。
图中带有负权路径时,贪心策略就失效了。

那为什么会这样呢?

因为按照Dijkstra算法的话
在这里插入图片描述
这里起点是s,所以第一次选到s,放到S集合里面,然后对s的相邻顶点进行松弛操作,更新距离s->t为10,s-y为5,所以第二次选到y,那y就被放到S集合里面了,而S是已经确定最短路径的结点集合,所以它这里的贪心策略就认为此时的5就是s->y的最短路径距离了(当然如果没有负权值的话这样是肯定正确的),y的最短路径已经确定了。
所以后面再去对t的相邻顶点进行松弛的时候就不会判断st+ty的距离是否小于sy,也不会再更新y的最短路径了,所以上面s->t->y就没有更新出来。因为每次都是从Q里面选的,而y前面已经放到S集合里面了。
但是有了负权值的话,sy的最短路径就不一定是5了(如果全是正的话肯定没问题),后面绕到其它的路径如果遇到负权值就可能会比5还小。

所以对于有负权值的图,Dijkstra算法就不再适用,这种贪心策略就失效了。

那对于有负权值的图我们如何求最短路径呢?

bellman—ford算法可以解决负权图的单源最短路径问题

这个我们下一篇文章就会讲到…

3. 源码

void Dijkstra(const V& src, vector<int>& pPath, vector<W>& dist)
{//初始化一下记录路径和权值(距离)的数组size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();pPath.resize(n, -1);dist.resize(n, MAX_W);//集合S为已确定最短路径的顶点集合,这里我们用一个数组表示S集合就可以//,初始化全false(S中无结点),表示所有顶点都在Q中,//不在S就在Q(其余未确定最短路径的结点集合)vector<bool> s(n, false);pPath[srci] = srci;dist[srci] = 0;//n个结点,需要选择n次去更新路径for (size_t i = 0; i < n; i++){int u = 0;W minDist = MAX_W;//从Q 中找出一个从起点到该结点代价最小的结点ufor (size_t j = 0; j < n; j++){if (s[i] == false && dist[i] < minDist){u = i;minDist = dist[i];}}//将u 从Q 中移出,并放入S 中s[u] = true;//对u 的每一个相邻结点v 进行松弛操作,如果src->u + u->v < src->v ,就更新距离for (size_t 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];//同时更新记录路径的数组pPathpPath[v] = u;}}}}void ptintMinPath(const V& src, const vector<int>& pPath, const vector<W>& dist)
{size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();for (size_t i = 0; i < n; i++){if (i != srci)//起点-》起点就没必要打印了{//定义一个vector保存路径上的结点vector<int> path;//从当前结点开始顺着父结点往上走size_t parenti = i;while (parenti != srci){path.push_back(parenti);parenti = pPath[parenti];}path.push_back(srci);//逆置path数组reverse(path.begin(), path.end());//打印路径for (auto e : path){cout << _vertexs[e] << "->";}//打印权值cout << dist[i] << endl;}}
}
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);vector<int> dist;vector<int> pPath;g.Dijkstra('s', dist, pPath);g.ptintMinPath('s', dist, pPath);*/// 图中带有负权路径时,贪心策略则失效了。// 测试结果可以看到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);vector<int> dist;vector<int> pPath;g.Dijkstra('s', dist, pPath);g.ptintMinPath('s', dist, pPath);
}

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

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

相关文章

Unity——数据存储的几种方式

一、PlayerPrefs PlayerPrefs适合用于存储简单的键值对数据 存储的数据会在游戏关闭后依然保持&#xff0c;并且可以在不同场景之间共享&#xff0c;适合用于需要在游戏不同场景之间传递和保持的数据。 它利用key-value的方式将数据保存到本地&#xff0c;跟字典类似。然后通…

【排序算法】详解直接插入排序和希尔排序原理及其性能分析

文章目录 插入排序算法原理细节分析代码实现复杂度分析:稳定性分析:与冒泡排序的对比 希尔排序算法原理细节分析代码实现复杂度分析稳定性分析 总结对比 插入排序 算法原理 插入排序又或者说直接插入排序,是一种和冒泡排序类似的并且比较简单的排序方法&#xff0c; 基本思想…

android aidl Can‘t resolve Salary问题

** 解决方法 ** 在数据aidl中写上包名&#xff0c;并且保证他的实体类也应当在与数据aidl文件相同的java类的包下 如果实例类放在aidl文件中会报一些稀奇古怪的错误&#xff0c;可以生成.java文件但是里面的实体类是不能识别的

Swagger使用

Swagger 简介 号称世界上最流行的API框架&#xff1b;Restful API 文档在线生成工具 —> API文档与API定义同步更新直接运行&#xff0c;可以在线测试 API 接口&#xff1b;支持各种语言&#xff1b;&#xff08;Java&#xff0c;PHP…&#xff09; 官网 Spring Boot 集…

BUUCTF学习(5): 命令执行Ping

1、介绍 2、解题 127.0.0.1|cat /flag 结束

Unity-3D模型展示

将3D模型放置到某个位置&#xff0c;然后通过鼠标左键进行旋转的操作 一种方法是添加另外的相机&#xff0c;采用RenderTexture来渲染该相机的内容 那么RenderTexture是做什么的呢&#xff1f; RenderTexture可以捕获从摄像机、光源和其他对象渲染的图像&#xff0c;并将结果…

如何使用FME开发自动化分析报告功能

目录 前言 一、使用的技术栈 二、技术难点解析 1.专题图 2.WORD文档实现 2.1 动态标题 2.3动态表格和文本 2.3专题图插入 三、完成NewGIS部署 四、模板总览图 总结 前言 一个标准项目分析报告需要需要包括3个方面&#xff1a; 文本叙述&#xff0c;主要体现在对某项专项数据的…

cmd进程简单操作指令

dir 查询当前路径和子路径 start空格加自己的exe程序&#xff0c;可运行程序。 taskkill /?可以执行很多&#xff0c;通常用于结束程序。 taskkill /f /im qq.exe 启动nginx.exe 查看运行的进程有哪些 选择结束nginx.exe

Python文件共享+cpolar内网穿透:轻松实现公网访问

文章目录 1.前言2.本地文件服务器搭建2.1.Python的安装和设置2.2.cpolar的安装和注册 3.本地文件服务器的发布3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 数据共享作为和连接作为互联网的基础应用&#xff0c;不仅在商业和办公场景有广泛的应用&#…

数据结构----算法--五大基本算法

数据结构----算法–五大基本算法 一.贪心算法 1.什么是贪心算法 在有多个选择的时候不考虑长远的情况&#xff0c;只考虑眼前的这一步&#xff0c;在眼前这一步选择当前的最好的方案 二.分治法 1.分治的概念 分治法&#xff1a;分而治之 将一个问题拆解成若干个解决方式…

uni-app--》基于小程序开发的电商平台项目实战(六)

&#x1f3cd;️作者简介&#xff1a;大家好&#xff0c;我是亦世凡华、渴望知识储备自己的一名在校大学生 &#x1f6f5;个人主页&#xff1a;亦世凡华、 &#x1f6fa;系列专栏&#xff1a;uni-app &#x1f6b2;座右铭&#xff1a;人生亦可燃烧&#xff0c;亦可腐败&#xf…

docker入门加实战—docker常见命令

docker入门加实战—docker常见命令 在介绍命令之前&#xff0c;先用一副图形象的展示一下docker的命令&#xff1a; 常见命令 docker的常见命令和文档地址如下表&#xff1a; 命令说明文档地址docker pull拉取镜像docker pulldocker push推送镜像到DockerRegistrydocker pus…

EmoTalk: Speech-Driven Emotional Disentanglement for 3D Face Animation

问题:现存的方法经常忽略面部的情感或者不能将它们从语音内容中分离出来。 方法:本文提出了一种端到端神经网络来分解语音中的不同情绪,从而生成丰富的 3D 面部表情。 1.我们引入了情感分离编码器(EDE),通过交叉重构具有不同情感标签的语音信号来分离语音中的情感和内容。…

【广州华锐互动】塔吊多人安拆VR互动培训系统

塔吊多人安拆VR互动培训系统由广州华锐互动制作&#xff0c;是一种基于VR技术的模拟实训系统&#xff0c;专门用于培训塔吊驾驶员和操作员。 在现实生活中&#xff0c;塔吊操作具有一定的危险性&#xff0c;尤其是在培训过程中容易发生意外。而使用VR互动实训系统&#xff0c;学…

金融用户实践|分布式存储支持数据仓库业务系统性能验证

作者&#xff1a;深耕行业的 SmartX 金融团队 闫海涛 估值是指对资产或负债的价值进行评估的过程&#xff0c;这对于投资决策具有重要意义。每个金融公司资管业务人员都期望能够实现实时的业务估值&#xff0c;快速获取最新的数据和指标&#xff0c;从而做出更明智的投资决策。…

Java8实战-总结42

Java8实战-总结42 用Optional取代null应用 Optional 的几种模式默认行为及解引用 Optional 对象两个 Optional 对象的组合使用 filter 剔除特定的值 用Optional取代null 应用 Optional 的几种模式 默认行为及解引用 Optional 对象 采用orElse方法读取这个变量的值&#xff0…

uniapp订单循环列表倒计时

目录 效果图片插件uni-countdown代码最后 效果图片 插件uni-countdown 地址 代码 <template><view class""><!-- 下面循环两个列表 --><view class"item" v-for"(item, index) in listData" :key"index">&…

mybatis-plus自动填充

前言 这是我在这个网站整理的笔记&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 mybatis-plus自动填充 大家做设计数据表的时候&#xff0c;基本上都会有del_flag&#xff0c;create_time, update_time,这三个字段&#xff0c;这也是…

问题记录1 json解析问题

问题&#xff1a; json解析int类型不符合预期&#xff0c;使用json.NewDecoder解决。 示例如下&#xff1a; package mainimport ("bytes""encoding/json""fmt" )func main() {data1 : map[string]interface{}{}data1["id"] int64(4…

windows安装npm教程

在安装和使用NPM之前&#xff0c;我们需要先了解一下&#xff0c;NPM 是什么&#xff0c;能干啥&#xff1f; 一、NPM介绍 NPM&#xff08;Node Package Manager&#xff09;是一个用于管理和共享JavaScript代码包的工具。它是Node.js生态系统的一部分&#xff0c;广泛用于构…