数据结构-图-最短路径问题

news2024/10/6 5:58:22

最短路径问题

  • 单源最短路径
    • Dijkstra
      • 算法原理
      • 代码实现
    • Bellman-Ford
      • 算法原理
      • 代码实现
      • SPFA优化
      • SPFA代码实现
  • 多元最短路径
    • Floyd-Warshall
      • 算法原理
      • 代码实现

单源最短路径

🚀最短路径:从图G的某个顶点出发到达另一个顶点的最短路径,其中最短是指路径上边的权值和最小。
🚀单源最短路径:从图G中的某一顶点出发到达其余顶点的最短路径。

Dijkstra

算法原理

🚀针对一个带权有向图,将所有的顶点划分为两个集合S和Q,S是已经确定最短路径的顶点结合,Q是还未确定最短路径的顶点集合。每次从Q中找出一个从起点s到达该结点代价最小(权值和最小)的结点u(可见迪杰斯特拉算法同样采取的是贪心策略),将u从集合Q中移除,并放入S中,对u所邻接的顶点做一次松弛操作。松弛操作即对结点u的邻接点v,判断从起点s到u的代价+u到v的代价是否小于s到v的代价,若小于那么对s到v的代价替换为s到u的代价+u到v的代价。这样依次从Q中选出结点u,直到Q为空为止。

算法特点:比较与其他最短路径的算法,迪杰斯特拉算法的效率较优。但是,此算法只能用于没有负权值的图。

在这里插入图片描述

源点为s,即求s到达其他顶点的最短路径。在初始时,s到达其他边的路径长度均为无穷大 ,到达自身的距离为0。

算法执行过程

在这里插入图片描述

代码实现

void Dijkstra(const V& src, std::vector<W>& dist, std::vector<int>& parent_path) {
	//初始化工作
	size_t n = _vertex.size();
	size_t srci = this->GetVertexIndex(src);
	dist.resize(n, W_MAX);
	dist[srci] = 0;
	parent_path.resize(n, -1);
	std::vector<bool> S(n, false); //已选出的顶点集合S
	for (size_t i = 0; i < n; i++) {
		//从dist中选出最短的一条边,S->u
		size_t u = -1;
		W min_eg = W_MAX;
		for (size_t j = 0; j < n; j++) {
			if (dist[j] < min_eg && false == S[j]) {
				u = j;
				min_eg = dist[j];
			}
		}
		//将这个顶点添加到S集合中
		S[u] = true;
		//更新从起始点srci能够到达的其他点的最短路径dist数组
		for (size_t i = 0; i < n; i++) {
			if (_matrix[u][i] != W_MAX && false == S[i] && dist[u] + _matrix[u][i] < dist[i]) {
				dist[i] = dist[u] + _matrix[u][i];
				parent_path[i] = u;
			}
		}
	}
}

dist数组和parent_path数组解释:将源点s到达其他顶点的最短路径数值存储在dist数组中,dist[i]即为源点s到达i号顶点的最短距离。parent_path数组用来记录源点s到达其他顶点的最短路径,采用的是双亲表示法,即该结点存储的内容为源点s到达这个结点路径上的上一个结点的下标。

🚀上面例子中最终两个数值的内容如下:

在这里插入图片描述

Bellman-Ford

算法原理

🚀迪杰斯特拉算法不能解决带负权值路径的情况,贝尔曼算法可以解决这一问题。相比于迪杰斯特拉的贪心策略,贝尔曼算法是一种较为暴力的解法,即对每一个结点都做一次松弛操作,但是一轮松弛操作往往不能得到最终结果,存在最短路径于最短路径权值和对应不上的情况,所以要经过多轮更新才能得到最终结果。最多轮次为n-1轮(n:顶点个数),如果说第n轮更新还能有顶点松弛成功说明存在权值和为负数的环路情况,这种情况贝尔曼算法也是解决不了的(权值和为负数的环路意味着每轮更新都能得到一个更小的结果,是无休止的)。

只经过一轮更新不能的到最终结果的例子

在这里插入图片描述

产生上图中这样结果的原因在于更新源点s到达z顶点的最短路径是由对t顶点的邻接顶点做松弛操作得到的,但是在更新源点s到达z的最短路径之后,在对x的邻接顶点做松弛操作的时候,又重新修改了源点s到达t顶点的最短路径,此时在parent_path数组中t位置的数据修改成了x的下标,同样也因此导致了s到达z顶点的路径于路径上的权值和不一致的情况。所以要对t的邻接顶点重新做一次松弛操作,上面这种情况再对t的邻接顶点做一次松弛操作就可以解决问题,但是对于其他更为复杂的问题可能仍旧需要新一轮的松弛操作。那么,松弛操作轮数的上限是多少次呢?n - 1次因为源点出发到达另一个顶点的最短路径至多经过n - 1条边,所以至多经过n - 1轮的更新就能得到最终结果,但是如果第n轮时仍旧存在到达某个点的最短路径发生了更新,那么就说明存在权值和为负数的环路问题。

代码实现

bool Bellman(const V& src, std::vector<W>& dist, std::vector<int>& parent_path) {
	//初始化结构
	size_t n = _vertex.size();
	size_t srci = this->GetVertexIndex(src);
	dist.resize(n, W_MAX);
	dist[srci] = 0;
	parent_path.resize(n, -1);
	//寻找最短路径
	for (size_t k = 0; k < n - 1; ++k) {
		bool update = false;
		//update小的优化--如果此轮更新中没有任何一条边松弛成功,此时就可以break退出
		std::cout << "第" << k + 1 << "轮更新: \n";
		for (size_t i = 0; i < n; i++) {
			for (size_t j = 0; j < n; j++) {
				if (_matrix[i][j] != W_MAX && dist[i] + _matrix[i][j] < dist[j]) {
					update = true;
					dist[j] = dist[i] + _matrix[i][j];
					std::cout << i << "->" << j << ":" << dist[j] << std::endl;
					parent_path[j] = i;
				}
			}
		}
		if (false == update) {
			break;
		}
	}
	//如果经过n-1轮还能进行更新说明出现了负权值环路问题
	for (size_t i = 0; i < n; i++) {
		for (size_t j = 0; j < n; j++) {
			if (_matrix[i][j] != W_MAX && dist[i] + _matrix[i][j] < dist[j]) {
				return false;
			}
		}
	}
	return true;
}

SPFA优化

🚀在上面分析一轮的松弛操作可能不能得到最终结果的问题时,解决方案就是再对t顶点的邻接顶点做一次松弛操作即可,并不用再对其他顶点做一次松弛操作,也就是说如果要在下一轮中再次对某个顶点的邻接顶点做松弛操作,那么这个顶点一定在本轮中得到了最短路径的更新,否则其不会对其他顶点产生影响。

🚀SPFA算法就是对上面代码中写的贝尔曼算法的一个优化,就是说如果要在下一轮中再次对某个顶点的邻接顶点做松弛操作,那么这个顶点一定在本轮中得到了最短路径的更新,所以在第一轮更新中所有顶点都需要更新,把所有的顶点都入队列,在后续的更新中,如果某个顶点在本轮没有被更新那么其也不会对其他顶点产生影响,就不用再次入队列,相反需要再次入队列, 这样循环置队列为空即可。

SPFA代码实现

void BellmanSPFA(const V& src, std::vector<W>& dist, std::vector<int>& parent_path) {
	//初始化结构
	size_t n = _vertex.size();
	size_t srci = this->GetVertexIndex(src);
	dist.resize(n, W_MAX);
	dist[srci] = 0;
	parent_path.resize(n, -1);
	std::queue<size_t> q;
	std::vector<bool> flag(n, false);
	q.push(srci);
	flag[srci] = true;
	while (!q.empty()) {
		size_t top = q.front();
		q.pop();
		flag[top] = false;
		for (size_t i = 0; i < n; i++) {
			if (_matrix[top][i] != W_MAX && dist[top] + _matrix[top][i] < dist[i]) {
				dist[i] = dist[top] + _matrix[top][i];
				parent_path[i] = top;
				if (flag[i] == false) {
					q.push(i);
					flag[i] = true;
				}
			}
		}
	}
}

多元最短路径

Floyd-Warshall

算法原理

🚀佛洛依德算法是一个解决多源的最短路径问题的经典算法,它能够计算出每个顶点到达其余顶点的最短路径,对应用场景通常是带负权值的多源最短路径问题。

🚀佛洛依德算法采用的是动态规划的思想,顶点i到达顶点j的最短路径上至少经过了0个其他顶点,至多经过了n - 2个其他顶点,其状态标识可以定义为dp[i][j][m],标识顶点i到达顶点j的最短路径上经过了k个其余顶点,其余顶点就是除了起始顶点和终止顶点的其他顶点记作k(k有n-2种取值可能),所以如果i到j的最短路径经过k,那么dp[i][j][m] = dp[i][k][m-1] + dp[k][j][m-1],如果i到j的最短路径不经过k,dp[i][j][m] = dp[i][j][m-1],所以最终dp[i][j][m] = min(dp[i][j][m-1],dp[i][k][m-1]+dp[k][j][m-1])。在正常写代码时通常将其优化为二维的动态规划,因为第三维的m总是依赖于m-1的。

代码实现

void Floyd(std::vector<std::vector<W>>& dist, std::vector<std::vector<int>>& parent_path) {
	//初始化结构
	size_t n = _vertex.size();
	dist.resize(n);
	parent_path.resize(n);
	for (size_t i = 0; i < n; ++i) {
		dist[i].resize(n, W_MAX);
		parent_path[i].resize(n, -1);
	}
	//初始化直接相连的边
	for (size_t i = 0; i < n; i++) {
		for (size_t j = 0; j < n; j++) {
			if (_matrix[i][j] != W_MAX) {
				dist[i][j] = _matrix[i][j];
				parent_path[i][j] = i;
			}
			if (i == j) {
				dist[i][j] = 0;
			}
		}
	}
	//动态规划
	for (size_t k = 0; k < n; ++k) {
		for (size_t i = 0; i < n; ++i) {
			for (size_t j = 0; j < n; ++j) {
				if (dist[i][k] != W_MAX && dist[k][j] != W_MAX &&
					dist[i][k] + dist[k][j] < dist[i][j]) {
					//更新
					dist[i][j] = dist[i][k] + dist[k][j];
					parent_path[i][j] = parent_path[k][j];
				}
			}
		}
	}
}

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

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

相关文章

鼠标灵敏度怎么调?4个方法提高使用体验感!

“不知道怎么回事&#xff0c;我的鼠标反应很慢慢&#xff0c;有时候好像会有延迟。使用起来感觉特别不舒服。这个问题应该怎么解决呢&#xff1f;希望大家帮帮我&#xff01;” 在使用电脑的过程中&#xff0c;鼠标是很常用的一个工具。调整鼠标的灵敏度对电脑的使用体验会有显…

DBeaver 导出数据的问题 SQL 错误: jdbc 驱动内部错误 Java heap space

DBeaver 导出结果集 报错 具体操作如下&#xff1a; 网友给出的解决方案是 &#xff1a;调整java参数 dbeaver.ini -startup plugins/org.eclipse.equinox.launcher_1.6.400.v20210924-0641.jar --launcher.library plugins/org.eclipse.equinox.launcher.win32.win32.x86_6…

第八章 排序 九、归并排序

一、定义 把两个或多个有序的序列合并成一个有序序列 二、2路归并 三、归并排序 四、例子 1、首先将数组A复制一份到另一个B数组。 2、在定义三个指针分别指向A数组头部、中间、尾部。定义两个指针指向B数组的头部和中间。 3、然后比较B数组中头指针和中间指针的数字大小&a…

Dockerfile自定义容器

1、Dockerfile Dockerfile 是用于构建 Docker 镜像的文本文件&#xff0c;其中包含一系列的指令和配置&#xff0c;用于定义镜像的构建过程。通过 Dockerfile&#xff0c;你可以定义镜像的基础操作系统、依赖、环境设置、应用程序等信息&#xff0c;从而实现可复制、自动化的镜…

mmcv视频处理,如何遇到异常帧不中断

背景 mmcv读取视频帧如果遇到error&#xff0c;则会直接停止执行。但是&#xff0c;视频后面的内容有时候也十分重要&#xff0c;所以这个时候就需要一种方式可以继续处理后续帧。 处理方法 修改mmcv的VideoReader中的__next__函数&#xff0c;修改为如下&#xff1a; def _…

Django使用SMTP发送邮件教程

CONTENTS 1. SMTP介绍2. 申请邮箱授权码3. Django发送邮件 1. SMTP介绍 SMTP&#xff08;Simple Mail Transfer Protocol&#xff09;即简单邮件传输协议&#xff0c;它是一组用于由源地址到目的地址传送邮件的规则&#xff0c;由它来控制信件的中转方式。SMTP 协议属于 TCP/I…

fiddler 的用法

使用fiddler进行抓包 举例一些常见的代理工具 1.wireshark:功能非常强大,但是使用起来更复杂一些 2.fiddler:功能虽然比wireshark少,但是使用简单方便,功能也足以应付大部分场景了 安装了fiddler之后,http和https的请求和响应都会被抓包工具捕捉到,其他的不会 打开软件,左侧是…

网络安全:六种常见的网络攻击手段

1、什么是VPN服务&#xff1f; 虚拟专用网络&#xff08;或VPN&#xff09;是您的设备与另一台计算机之间通过互联网的安全连接。VPN服务可用于在离开办公室时安全地访问工作计算机系统。但它们也常用于规避政府审查制度&#xff0c;或者在电影流媒体网站上阻止位置封锁&#…

mybatis:mybatis-generator插件使用

mybatis&#xff1a;mybatis-generator插件使用 1 idea配置 idea&#xff0c;点击File->Settings->Plugins->设置&#xff0c;点击&#xff1a;Manage Plugin Repositories: 配置如下&#xff1a; http://plugins.jetbrains.com/下载插件并重启idea&#xff1a; 2 …

Avalonia环境搭建

1.开发文档 开发文档&#xff0c; GitHub项目地址 https://github.com/avaloniaui/avalonia 2.VS2022 及扩展安装 建议使用vs2022最新版本下载并安装扩展Avalonia for Visual Studio 2022 3.安装Avalonia UI模板 dotnet new install Avalonia.Templates 查看安装版本 dot…

3 个令人惊艳的 AI 文档神器,开源了!

公众号关注 “GitHubDaily” 设为 “星标”&#xff0c;每天带你逛 GitHub&#xff01; 现今的互联网时代&#xff0c;无论哪个行业&#xff0c;在日常各种工作的沟通协作中&#xff0c;我们都免不了要与各种文档打交道。 但是&#xff0c;人的精力与任务处理速度毕竟有限&…

WinApp自动化测试之工具的选择

WinApp&#xff08;Windows APP&#xff09;是运行在Windows操作系统上的应用程序&#xff0c;通常会提供一个可视的界面&#xff0c;用于和用户交互。 例如运行在Windows系统上的Microsoft Office、PyCharm、Visual Studio Code、Chrome&#xff0c;都属于WinApp。常见的WinA…

8. Java本项目知识基础---下篇

Java本项目知识基础—下篇 1. String 拼接字符串 进行头部、中部、尾部拼接 &#xff08;简单、内存占用大&#xff09; StringBuffer或StringBuilder进行append拼接&#xff08;复杂、内存占用小&#xff09; public static void main(String[] args) {String str "abc…

Z410 2023款无人机,专为零基础开发者打造的入门级开源无人机

为什么开发Z410升级款-Easydrone无人机 新手开发者通常在本科阶段加入人工智能行业&#xff0c;对无人机二次开发往往一知半解&#xff0c;面临着C、Python、ROS和mavlink等一系列入门知识&#xff0c;学习起来非常困难&#xff0c;学习的过程中也面临许多挫折。为了帮助零基础…

vcruntime140.dll缺失如何修复,vcruntime140.dll重新安装方法分享

大家好&#xff01;今天&#xff0c;我非常荣幸能够站在这里&#xff0c;与大家分享关于vcruntime140.dll丢失的问题以及三种修复方法。希望通过我的演讲&#xff0c;能够帮助到在座的各位&#xff0c;解决我们在使用电脑过程中遇到的一些问题。 首先&#xff0c;让我们来了解一…

京东数据分析平台:9月中上旬白酒消费市场数据分析

9月份&#xff0c;围绕白酒的热点不断。9月5日&#xff0c;瑞幸咖啡官微发布消息称&#xff0c;瑞幸与贵州茅台跨界合作推出的酱香拿铁刷新单品纪录&#xff0c;首日销量突破542万杯&#xff0c;销售额破1亿元。9月14日&#xff0c;贵州茅台官微发布消息称与德芙推出联名产品“…

VMProtect使用教程(VC++MFC中使用)

VMProtect使用教程(VCMFC中使用) VMProtect是一种商业级别的代码保护工具&#xff0c;可以用于保护VC MFC程序。以下是使用VMProtect保护VC MFC程序的步骤&#xff1a; 1. 下载并安装VMProtect,C包含库及目录。 2. 在VC MFC项目中添加VMProtectSDK.h头文件&#xff0c;并在需…

Spring Boot如何配置CORS支持

Spring Boot如何配置CORS支持 CORS&#xff08;跨源资源共享&#xff09;是一种Web浏览器的安全性功能&#xff0c;用于控制网页上的脚本文件从不同的源加载其他网页资源。在开发现代Web应用程序时&#xff0c;通常需要跨域请求不同的资源&#xff0c;如API服务或其他Web应用程…

STM32CubeMX学习笔记-RTC实时时钟使用

STM32CubeMX学习笔记-RTC实时时钟使用 一、RTC简介二、新建工程三、RTC3.1 选择时钟3.3 生成代码3.5 添加读取时间函数 原创链接 1 一、RTC简介 实时时钟&#xff08;RTC&#xff09; 是一个独立的 BCD 定时器/计数器。 RTC 提供具有可编程闹钟中断功能的日历时钟/日历。RTC 还…

java开源商城免费搭建 VR全景商城 saas商城 b2b2c商城 o2o商城 积分商城 秒杀商城 拼团商城 分销商城 短视频商城

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框架…