[高阶数据结构六]最短路径算法

news2024/12/23 16:30:58

1.前言

最短路径算法是在图论的基础上讲解的,如果你还不知道图论的相关知识的话,可以阅读下面几篇文章。

[高阶数据结构四] 初始图论_初始图结构-CSDN博客

[高阶数据结构五] 图的遍历和最小生成树_图的遍历和生成树求解-CSDN博客

本章重点:

本章主要讲解图论的三种算法---DijkstraBallman-Ford,Floyd-Warshall算法。

2.单源最短路径

所谓的单源最短路径,也就是从图中任意一点出发, 到图中每个节点的最短路径,也就是最小的权值和。

举个例子:

为了要统计源点Srci即s到yztx四点的最短路径,通常使用两个数组来解决。

其中一个表示的是,从源点到当前点的最短路径的权值

另外一个表示的是,从源点到当前点最短路径的路径是什么。

//存储任意点到图中其他点的最短路径的权值
 vector<W>& dist
 //记录srci->其他顶点最短路径父顶点数组
 vector<int>& parentPath

这么说可能有点抽象,看如下图。

数组下标x处,对应的是t所在位置的下标,而t处对应的下标所在的位置是y。

dist中dist[4]表示从源点到x的最短路径为9。

3.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 的代价之和,否则维持原样。

这样说有点抽象--但是如果你阅读了前面两篇文章,你会发现这个算法和Prim算法是有点类似的。

举例如下:

a处唯一确定的节点就是s,其余均是不确定的。在这里面找出一个与A直接相连,且路径是最短的出来,然后这个值那么一定是可以唯一确定的。

b处也用a处的方法,然后可以确定的节点是s,y,其余均为不确定的。

依次重复,有几个结点那么重复几次,一定可以把所有的节点确定下来。(这里的前提是权值均为正,不可以有负的)。后续解释为什么。

代码如下:

void Dijkstra(const V& src, vector<W>& dist, vector<int>& parentPath)
		{
			//dist[i]表示srci到i位置的最短路径的权值
			//parentPath[i]表示推导出当前节点的下标.
			//即srci->B位置的最小权值是由srci->A->B推导而来,所以存储的是A的下标
			size_t n = _ver.size();
			dist.resize(n, int_MAX);
			parentPath.resize(n, -1);//-1表示初始时,没有路径能走到
			int srci = GetIndex(src);
			dist[srci] = 0;
			dist[srci] = W();
			parentPath[srci] = srci;
			vector<bool> visited(n, false);
			//n个节点,一共会更新n次,也可以判断visited数组中的元素是否全为true
			for (int s = 0; s < n; s++)
			{
				//每次找出dist中没有遍历过的,当前的最小的权值来做确定的顶点及边
				int u = 0;//表示顶点
				int min = int_MAX;//表示srci->u的权值
				for (int i = 0; i < n; i++)
				{
					if (visited[i] == false && dist[i] < min)
					{
						u = i;
						min = dist[i];
					}
				}
				//到这里就找到了最小的
				visited[u] = true;

				//开始进行松弛操作,即从srci->u->v的权值是否比srci->v的权值小,小的话就在dist中换掉
				for (int v = 0; v < n; v++)
				{
					//这里松弛操作还有一个关键点,就是已经选定了的,即在visited里面是true的,后续哪怕找到了
					//比当前小的路径,也不在进行松弛操作了。理论上是不可能的,含有负权值除外
					if (visited[v]==false
						&&_martix[u][v] != int_MAX 
						&& dist[v] > dist[u] + _martix[u][v])
					{
						dist[v] = dist[u] + _martix[u][v];
						parentPath[v] = u;//表示u这个顶点->v顶点
					}
				}
			}
		}

这个算法是不支持有负权值的,这是因为一旦有了负权值,你之前唯一确定的点,可能就不是最短路径的值了。因为这里出现了环,那么可能出现的问题是:一直在环里面兜圈子,那么就会出现无穷小的情况。

例如:

4.BellMan-Ford算法

这个算法能够解决带有负权值的问题。

他是通过暴力的解法来搞定这个问题的。 它的时间复杂度是O(N^3). 它的思路就是以所有顶点为起始点,更新所有相连的边。

代码如下:

bool BellmanFord(const V& src, vector<W>& dist,vector<int>& parentPath)
		{
			size_t n = _ver.size();
			dist.resize(n, int_MAX);
			parentPath.resize(n, -1);//-1表示暂时所有的都没有通路

			int srci = GetIndex(src);
			dist[srci] = W();
			for (int k = 0; k < n-1; k++)
			{
				//加这个条件是有时候可能走3轮就不会再走后面的循环了,为了节省时间
				bool exchange = false;
				//开始暴力遍历n-1条边,并且更新权值
				cout << "第[" << k << "轮]" << endl;
				for (int i = 0; i < n; i++)
				{
					for (int j = 0; j < n; j++)
					{
						//srci ->i + i-> j 与srci->j的比较
						if (_martix[i][j] != int_MAX && dist[i] + _martix[i][j] < dist[j])
						{
							cout << "[" << _ver[i] << "]" << "->" << _ver[j] << ":" \
								<< _martix[i][j] << endl;
							dist[j] = dist[i] + _martix[i][j];
							parentPath[j] = i;
							exchange = true;
						}
					}
				}
				//到这里第一遍n-1条边的权值已经更新完了,但是有可能导致的因数是前面确定的边的值可能不是最小值,
				//前面确定边的值必须由后面确定边的值推导而来。一次暴力遍历n-1条边,可以至少确定一个顶点,有n个
				//顶点,所以至少要遍历n-1遍
				if (exchange == false) break;
			}

			//这里判断负权值的回路是否构成了环
			for (int i = 0; i < n; i++)
			{
				for (int j = 0; j < n; j++)
				{
					//srci ->i + i-> j 与srci->j的比较
					if (_martix[i][j] != int_MAX && dist[i] + _martix[i][j] < dist[j])
					{
						return false;
					}
				}
			}
			return true;
		}

5.多源最短路径问题

简单的来说多源最短路径问题就是任意两点间的最短路径的权值以及走过的节点是什么。

那么这就需要两个二维数组来表示上述两个情况了。

 一个二维数组存储顶点i->j的最短路径的权值和, 另外一个数组存储 (i,j)的父节点下标. i,j是最短路径的节点。

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

举个例子:

代码如下:

void FloydWarShall(vector<vector<W>>& vvDist, vector<vector<int>>& vvParentPath)
		{
			//任意两点的最短路径
			//1.初始化
			size_t n = _ver.size();
			vvDist.resize(n);
			vvParentPath.resize(n);
			for (int i = 0; i < n; i++)
			{
				vvDist[i].resize(n, int_MAX);
				vvParentPath[i].resize(n, -1);
			}

			//先初始化那些直接相连的
			for (int i = 0; i < n; i++)
			{
				for (int j = 0; j < n; j++)
				{
					if (i == j)
					{
						vvDist[i][j] = 0;
						vvParentPath[i][j] = -1;
					}
					if (_martix[i][j] != int_MAX)
					{
						vvDist[i][j] = _martix[i][j];
						vvParentPath[i][j] = i;
					}
				}
			}

			//任意两点间的最短路径---abcdef  求a->f之间的最短路径,假设中间点为k,
            //可能经过k点,也可能不经过k点
			//若经过k点,dist[i][j]=dist[i][k]+dist[k][j]
			//不经过k点的话,那么dist[i][j]=dist[i][j],此时i-j之间是少了k点这个值的
			//那么问题就转换成找k点了,那么k点是谁呢? a->f k点可能是b c d e
			//若求的是b->e最短路径呢? 那么k点可能是a c e f。
			//通过上述分析发现,任意一个点都有可能成为k点。
			for (int k = 0; k < n; k++)
			{
				for (int i = 0; i < n; i++)
				{
					for (int j = 0; j < n; j++)
					{
						if (vvDist[i][k] != int_MAX && vvDist[k][j] != int_MAX
							&& vvDist[i][k] + vvDist[k][j] < vvDist[i][j])
						{
							vvDist[i][j] = vvDist[i][k] + vvDist[k][j];
							vvParentPath[i][j] = vvParentPath[k][j];
                            //这里为什么不直接是k呢?
							//首先如果k是推出j的上一个顶点,那么vvp[k][j]一定放的是k
							//那么如果k不是推出j的上一个顶点呢?那么就不能填k,
                            //但是上一个顶点一定存放在vvp[k][j]里面
						}
					}
				}
			}

7.总结

这些算法都比较抽象,博主花了很大很大的功夫才理解他们,如果真要完全手撕,那么我想博主起码最少需要几个小时,因此就算不会手撕也没有关系,只需要知道思路即可。在面试中,能讲出思路也是一个加分项呢。

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

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

相关文章

开源的跨平台SQL 编辑器Beekeeper Studio

一款开源的跨平台 SQL 编辑器&#xff0c;提供 SQL 语法高亮、自动补全、数据表内容筛选与过滤、连接 Web 数据库、存储历史查询记录等功能。该编辑器支持 SQLite、MySQL、MariaDB、Postgres 等主流数据库&#xff0c;并兼容 Windows、macOS、Linux 等桌面操作系统。 项目地址…

mysql 5.7安装及安装后无法启动问题处理

下载安装包&#xff0c;直接解压 配置环境变量 创建my.ini文件 [mysqld] #端口号 port 3306 #mysql-5.7.27-winx64的路径 basedirD:/soft/mysql57 #mysql-5.7.27-winx64的路径\data datadirD:/soft/mysql57/data #最大连接数 max_connections200 #编码 character-set-server…

spine 动画层 动态权重

前奏.业务背景 这边想实现一个功能&#xff0c;项目中有 一只猫 猫的头会盯着逗猫棒移动。因为素材还没到所以这里使用了 spine 自带的猫头鹰。他的动画 刚好挺有针对性&#xff1a;&#xff08;关联上篇&#xff09;https://blog.csdn.net/nicepainkiller/article/details/144…

Spark 内存管理机制

Spark 内存管理 堆内内存和堆外内存 作为一个 JVM 进程&#xff0c;Executor 的内存管理建立在 JVM(最小为六十四分之一&#xff0c;最大为四分之一)的内存管理之上&#xff0c;此外spark还引入了堆外内存&#xff08;不在JVM中的内存&#xff09;&#xff0c;在spark中是指不…

Vision Transformer(vit)的主干

图解&#xff1a; 代码&#xff1a; class VisionTransformer(nn.Module):def __init__(self, img_size224, patch_size16, in_c3, num_classes1000,embed_dim768, depth12, num_heads12, mlp_ratio4.0, qkv_biasTrue,qk_scaleNone, representation_sizeNone, distilledFalse,…

mongodb配置ssl连接

mongodb5.0.9 centos7.6x86 1、正常启动mongod -f mongodb.conf 2、生成所需要的ssl证书 服务端ssl配置&#xff1a; 2.1生成ca.pem证书 #-x509&#xff1a; 用于生成自签证书&#xff0c;如果不是自签证书则不需要此项 #-days: 证书的有效期限&…

Linux 中的 ls 命令:从使用到源码解析

ls 命令是 Linux 系统中最常用和最基本的命令之一。下面将深入探讨 ls 命令的使用方法、工作原理、源码解析以及实际应用场景。 1. ls 命令的使用** ls 命令用于列出目录内容&#xff0c;显示文件和目录的详细信息。 1.1 基本用法 ls [选项] [文件或目录]例如&#xff1a; …

Python 【图像分类】之 PyTorch 进行猫狗分类功能的实现(Swanlab训练可视化/ Gradio 实现猫狗分类 Demo)

Python 【图像分类】之 PyTorch 进行猫狗分类功能的实现(Swanlab训练可视化/ Gradio 实现猫狗分类 Demo) 目录 Python 【图像分类】之 PyTorch 进行猫狗分类功能的实现(Swanlab训练可视化/ Gradio 实现猫狗分类 Demo) 一、简单介绍 二、PyTorch 三、CNN 1、神经网络 2、卷…

【C语言】结构体(二)

一&#xff0c;结构体的初始化 和其它类型变量一样&#xff0c;对结构体变量可以在定义时指定初始值 #include <stdio.h> #include <stdlib.h> struct books // 结构体类型 {char title[50];char author[50]; //结构体成员char subject[100];int book_id; }…

C++(4个类型转换)

1. C语言中的类型转换 1. 隐式 类型转换&#xff1a; 具有相近的类型才能进行互相转换&#xff0c;如&#xff1a;int,char,double都表示数值。 2. 强制类型转换&#xff1a;能隐式类型转换就能强制类型转换&#xff0c;隐式类型之间的转换类型强相关&#xff0c;强制类型转换…

Windows下从命令行(Powershell/CMD)发送内容到系统通知中心

Windows下从命令行&#xff08;Powershell/CMD&#xff09;发送内容到系统通知中心 01 前言 在平时写脚本的时候&#xff0c;将日志等信息直接输出到控制台固然是最直接的&#xff0c;而如果是一些后台执行的任务&#xff0c;不需要时刻关注运行细节但是又想知道一些大致的情…

四、初识C语言(4)

一、作业&#xff1a;static修饰局部变量 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <string.h> //作业&#xff1a;static修饰局部变量 int sum (int a) {int c 0;static int b 3;c 1;b 2;return (abc); } int main() {int i 0;int a …

基于深度学习的甲状腺结节影像自动化诊断系统(PyQt5界面+数据集+训练代码)

随着医学影像技术的发展&#xff0c;计算机辅助诊断在甲状腺结节的早期筛查中发挥着重要作用。甲状腺结节的良恶性鉴别对临床治疗具有重要意义&#xff0c;但传统的诊断方法依赖于医生的经验和影像学特征&#xff0c;存在一定的主观性和局限性。为了解决这一问题&#xff0c;本…

VLC 播放的音视频数据处理流水线搭建

VLC 播放的音视频数据处理流水线搭建 音视频流播放处理循环音频输出处理流水线VLC 用 input_thread_t 对象直接或间接管理音视频播放有关的各种资源,包括 Access, Demux, Decode, Output, Filter 等,这个类型定义 (位于 vlc-3.0.16/include/vlc_input.h) 如下: s…

Android 12系统源码_RRO机制(一)Runtime Resource Overlay机制实践

前言 Android的RRO&#xff08;Runtime Resource Overlay&#xff09;机制允许开发者在运行时替换或重写系统资源&#xff0c;例如布局、图标、字符串等。这个机制的目标是为了支持设备定制和主题化&#xff0c;特别是在不修改系统源代码的情况下。RRO通过在系统的资源上叠加一…

Tomcat新手成长之路:安装部署优化全解析(下)

接上篇《Tomcat新手成长之路&#xff1a;安装部署优化全解析&#xff08;上&#xff09;》: link 文章目录 7.应用部署7.1.上下文7.2.启动时进行部署7.3.动态应用部署 8.Tomcat 类加载机制8.1.简介8.2.类加载器定义8.3.XML解析器和 Java 9.JMS监控9.1.简介9.2.启用 JMX 远程监…

动态代理如何加强安全性

在当今这个信息爆炸、网络无孔不入的时代&#xff0c;我们的每一次点击、每一次浏览都可能留下痕迹&#xff0c;成为潜在的安全隐患。如何在享受网络便利的同时&#xff0c;有效保护自己的隐私和信息安全&#xff0c;成为了每位网络使用者必须面对的重要课题。动态代理服务器&a…

python---面向对象-python中的实践(2)

如何定义一个类&#xff1f; class 类名:pass怎样通过类&#xff0c;创建出一个对象&#xff1f; 根据类创建对象one Money() 执行流程1. 类的定义2. 根据类&#xff0c;创建出一个对象3. 将对象的唯一标识返回class Money:passprint(Money.__name__) xxx Money print(xxx.…

数据结构-散列函数的构造方法

一.数字关键词 关键词存储应该尽可能的离散 直接定址法:利用线性函数,例如上面的例子,h(key)key-1990,key1990&#xff0c;这个就被存放在0的位置 数字分析法:关键字可能有很到位组成,每一位变化可能都不一样&#xff0c;有的位是不变的,就是说不同的对象这一位都是一样的,有的…

单点登录解决方案 CAS(Central Authentication Service)详解

目录 CAS 的工作原理 票据&#xff08;Ticket&#xff09;详解 CAS 的优势 CAS 的应用场景 小结 参考资料 Central Authentication Service&#xff08;中央认证服务&#xff0c;简称 CAS&#xff09;是一个开源的企业级单点登录&#xff08;Single Sign-On, SSO&#xf…