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

news2025/7/18 22:03:57

文章目录

  • 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 中找出一个从起点到该结点代价最小的结点u
		for (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];
				//同时更新记录路径的数组pPath
				pPath[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.coloradmin.cn/o/1102742.html

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

相关文章

git学习——第4节 时光机穿梭

我们已经成功地添加并提交了一个readme.txt文件&#xff0c;现在&#xff0c;是时候继续工作了&#xff0c;于是&#xff0c;我们继续修改readme.txt文件&#xff0c;改成如下内容&#xff1a; Git is a distributed version control system. Git is free software. 现在&…

甘特图组件DHTMLX Gantt示例 - 如何有效管理团队工作时间?(二)

如果没有有效的时间管理工具&#xff0c;如工作时间日历&#xff0c;很难想象一个项目如何成功运转。这就是为什么我们的开发团队非常重视项目管理&#xff0c;并提供了多种选择来安排DHTMLX Gantt的工作时间。使用DHTMLX Gantt这个JavaScript库&#xff0c;您可以创建一个强大…

python:代码加密

一、将py文件打包成pyc文件 import py_compilepy_compile.compile(rsshare.py)二、机器码加密和解密 示例&#xff1a; 原始机器码输入&#xff1a;‘BRRWQZ8’ 加密机器码输出&#xff1a;‘QlJDDFFSMw’ import base64 # 要加密的字符串 original_string BRRDDL3 # 编码&…

基于R语言实现中介效应检验以及sobel检验代码

数据格式 随机数据&#xff0c;不一定好 y是因变量&#xff0c;x是自变量&#xff0c;m是中介变量 基本原理 M ~ X Y ~ X Y ~ X M 直接上代码 library(mediation) library(bda)# 加载readxl包 library(readxl) # 读取Excel表格# 读取数据 我是从剪切板读取的 data read…

标签页的使用

目录 1、引用TabSheet.h和TabSheet.cpp文件&#xff1a; 2、主窗口添加标签页&#xff1a; &#xff08;1&#xff09;、标签页的创建和属性更改 &#xff08;2&#xff09;、添加俩个标签页的类 &#xff08;3&#xff09;、主窗口添加成员变量 &#xff08;4&#xff09…

[补题记录] Atcoder Beginner Contest 324(E、F)

URL&#xff1a;https://atcoder.jp/contests/abc324 目录 E Problem/题意 Thought/思路 Code/代码 F Problem/题意 Thought/思路 Code/代码 E Problem/题意 给出 N 个字符串和 1 个 T 字符串&#xff0c;都由小写字母组成。 现在从 N 个字符串中任取 2 个拼接&…

电子邮件地址注册过程详解

许多人可能对如何注册电子邮件地址感到困惑&#xff0c;本文将详细解析电子邮件地址的注册过程&#xff1a;确定邮箱厂商、创建邮箱账户、设置电子邮件地址。 1、确定要注册的邮箱厂商 首先我们需要确定要注册哪种类型的电子邮件服务。目前市场上有许多不同的电子邮件服务提供商…

实现日期间的运算——C++

&#x1f636;‍&#x1f32b;️Take your time ! &#x1f636;‍&#x1f32b;️ &#x1f4a5;个人主页&#xff1a;&#x1f525;&#x1f525;&#x1f525;大魔王&#x1f525;&#x1f525;&#x1f525; &#x1f4a5;代码仓库&#xff1a;&#x1f525;&#x1f525;魔…

Java内存模型-Java Memory Model(JMM)-可见性、原子性、有序性

5. Java内存模型之JMM 5.1 先从大场面试开始 你知道什么是Java内存模型JMM吗&#xff1f; JMM和volatile他们两个之间的关系&#xff1f; JMM没有那些特征或者它的三大特征是什么&#xff1f; 为什么要有JMM&#xff0c;它为什么出现&#xff1f;作用和功能是什么&#xf…

C++数据结构X篇_15_求二叉树叶子数与高度(递归方法)

本篇参考求二叉树叶子数与高度&#xff08;C&#xff09;进行整理。 文章目录 1. 二叉树中叶子数与高度2. 求二叉树叶子数与高度的实现代码 1. 二叉树中叶子数与高度 我们首先来看一看二叉树中叶子数与高度的定义&#xff1a; 叶子数&#xff1a;对于一个二叉树的节点&#x…

【微信小程序】6天精准入门(第3天:小程序flex布局、轮播图组件及mock运用以及综合案例)附源码

一、flex布局 布局的传统解决方案&#xff0c;基于[盒状模型]&#xff0c;依赖display属性 position属性 float属性 1、什么是flex布局&#xff1f; Flex是Flexible Box的缩写&#xff0c;意为”弹性布局”&#xff0c;用来为盒状模型提供最大的灵活性。任何一个容器都可以…

基于五折交叉验证的支持向量机SVR回归预测研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

七大排序 (9000字详解直接插入排序,希尔排序,选择排序,堆排序,冒泡排序,快速排序,归并排序)

一&#xff1a;排序的概念及引入 1.1 排序的概念 1.1 排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在…

[Python中常用的回归模型算法大全2:从线性回归到XGBoost]

文章目录 概要多输出K近邻回归集成算法回归梯度提升决策树回归随机森林回归 概要 回归分析在数据科学领域扮演着关键角色&#xff0c;用于预测数值型目标变量。本文深入探讨了几种常用的回归模型&#xff0c;包括多输出K近邻回归&#xff0c;决策树回归&#xff0c;集成算法回…

告别手动调节!iOS 17让你全自动调节音量大小,那么如何实现个性化音量呢

多亏了iOS 17&#xff0c;你的AirPods Pro 2现在具有个性化音量功能&#xff0c;可以根据周围环境智能调整音频音量。 这很酷&#xff0c;任何喜欢尽可能降低音量以避免听力受损的人都会对此表示赞赏。使用个性化音量&#xff0c;你的iPhone将检测音量何时可以降低&#xff0c…

MyBatisPlus-02

一 查询条件的三种 1.按条件查询 //方式一&#xff1a;按条件查询QueryWrapper qw new QueryWrapper();qw.lt("age",18);List<User> userList userDao.selectList(qw);System.out.println(userList); 2.lambda格式按条件查询 //方式二&#xff1a;lambda格…

【前端学习】—函数节流(九)

【前端学习】—函数节流&#xff08;九&#xff09; 一、什么是函数节流 函数节流&#xff1a;规定在一个单位时间内&#xff0c;事件响应函数只能被触发一次&#xff0c;如果这个单位时间内触发多次函数&#xff0c;只有一次生效。 二、函数节流使用场景 window.onresize事…

pytorch代码实现之动态蛇形卷积模块DySnakeConv

动态蛇形卷积模块DySnakeConv 血管、道路等拓扑管状结构的精确分割在各个领域都至关重要&#xff0c;确保下游任务的准确性和效率。 然而&#xff0c;许多因素使任务变得复杂&#xff0c;包括薄的局部结构和可变的全局形态。在这项工作中&#xff0c;我们注意到管状结构的特殊…

安达发|大多数离散型生产模式适用APS自动排程系统

在离散型生产模式中&#xff0c;智能生产排程软件&#xff08;APS&#xff09;的应用越来越广泛。这是因为APS能够根据实时的生产需求和资源状况&#xff0c;自动进行生产计划的制定和调整&#xff0c;从而提高生产效率&#xff0c;降低生产成本&#xff0c;保证生产的顺利进行…