【数据结构】图的最短路径

news2024/11/24 0:17:59



快乐的流畅:个人主页


个人专栏:《C游记》《进击的C++》《Linux迷航》

远方有一堆篝火,在为久候之人燃烧!

文章目录

  • 引言
  • 一、最短路径的概念
  • 二、Dijkstra算法
    • 2.1 思想
    • 2.2 实现
  • 三、Bellman-Ford算法
    • 3.1 思想
    • 3.2 实现
  • 四、Floyd-Warshall算法
    • 4.1 思想
    • 4.2 实现
  • 五、Dijkstra、Bellman-Ford和Floyd-Warshall的对比
    • 5.1 适用场景
    • 5.2 时间复杂度
    • 5.3 松弛次序
    • 5.4 处理负权边与负权环
    • 5.5 总结表格
    • 5.6 选择算法的依据

引言

前置知识:【数据结构】图的概念和存储结构

一、最短路径的概念

最短路径(shortest path)问题,指的是在有向图中,从某一顶点出发,找出通往另一顶点的边权值和最小的路径。


在正式开始介绍最短路径算法之前,我们需要先了解一个核心操作——边松弛(Edge Relaxation)

首先,我们先说明,下图顶点中的值指的是该点到源点的距离。设S为源点,自身距离为0,而其他顶点未被遍历,内部为正无穷。

接下来,先遍历A点,则A点的值改为1,前驱结点为S。


其次,对点B进行同样操作,B点的值改为3,前驱结点为S。


最后,最关键的步骤来了,我们发现S->A->B比S->B路径更短(权值和更小),则将B点的值改为2,前驱结点改为A。


而最后这一步,就叫做边松弛,简称松弛。通过松弛操作,我们可以降低到达某点的距离(即为路径的权值和)。不断地进行松弛操作,最终我们便可以得到源点到所有点的最短距离。

二、Dijkstra算法

2.1 思想

Dijkstra算法是一种贪心算法:

  1. 每次先选出离源点距离最近的点,并纳入集合(表示该点的最短路径已确定)。
  2. 再对该点周围的边进行松弛。

由于 Dijkstra 算法假设一旦处理了某个节点,它的最短路径就已经确定,所以该算法不能处理带负权边的图。

2.2 实现

void Dijkstra(const V& src, vector<W>& dist, vector<int>& prev)
{
	int n = _vertexs.size();
	dist.resize(n, W_MAX);
	prev.resize(n, -1);
	vector<bool> S(n, false);

	int srci = GetIndex(src);
	dist[srci] = W();
	prev[srci] = srci;

	//<与源点的距离,目标点下标>
	priority_queue<pair<W, int>, vector<pair<W, int>>, greater<pair<W, int>>> minHeap;
	minHeap.push({ dist[srci],srci });

	while (!minHeap.empty())
	{
		//找到距离srci最近的点u
		auto top = minHeap.top();
		minHeap.pop();
		int u = top.second;
		S[u] = true;

		//对点u周围的边进行松弛
		for (int i = 0; i < n; ++i)
		{
			if (!S[i] && _edges[u][i] != W_MAX
				&& dist[u] + _edges[u][i] < dist[i])
			{
				dist[i] = dist[u] + _edges[u][i];
				prev[i] = u;
				minHeap.push({ dist[i],i });
			}
		}
	}
}

三、Bellman-Ford算法

3.1 思想

Bellman-Ford算法是一种基于动态规划的算法,不依赖贪心策略:

  • 对每条边进行松弛操作,共进行n-1次(n为顶点数量),每次迭代尝试松弛所有边。

ps:每一轮松弛时,不关注哪个节点已经处理,而是尝试通过所有边更新每个节点的距离。

Bellman-Ford 的松弛次序是全局的、重复的,它会反复松弛直到达到稳定状态,因此可以应对负权边的情况。

3.2 实现

bool BellmanFord(const V& src, vector<W>& dist, vector<int>& prev)
{
	int n = _vertexs.size();
	dist.resize(n, W_MAX);
	prev.resize(n, -1);

	int srci = GetIndex(src);
	dist[srci] = W();
	prev[srci] = srci;

	//每对所有边进行一次松弛,已确定的最短路径的边数加一(往外扩大一圈)
	for (int k = 0; k < n - 1; ++k)
	{
		bool update = false;
		for (int i = 0; i < n; ++i)
		{
			for (int j = 0; j < n; ++j)
			{
				if (_edges[i][j] != W_MAX
					&& dist[i] + _edges[i][j] < dist[j])
				{
					dist[j] = dist[i] + _edges[i][j];
					prev[j] = i;
					update = true;
				}
			}
		}

		if (!update)
		{
			break;
		}
	}

	//检测是否存在负权环路
	for (int i = 0; i < n; ++i)
	{
		for (int j = 0; j < n; ++j)
		{
			if (_edges[i][j] != W_MAX
				&& dist[i] + _edges[i][j] < dist[j])
			{
				return false;
			}
		}
	}
	return true;
}

四、Floyd-Warshall算法

4.1 思想

相比于前两个算法,Floyd-Warshall算法并不是基于单一源点的算法,而是直接计算图中所有点对之间的最短路径。

Floyd-Warshall算法是一个典型的动态规划算法:

  1. 依次遍历每个节点 k ,将它作为中间节点
  2. 对于每一对节点 (i, j) ,检查是否通过节点k 能获得更短的路径。如果是,则松弛点对 (i, j) 的距离。

4.2 实现

void FloydWarshall(vector<vector<W>>& dist, vector<vector<int>>& prev)
{
	int n = _vertexs.size();
	dist.resize(n, vector<W>(n, W_MAX));
	prev.resize(n, vector<int>(n, -1));

	for (int i = 0; i < n; ++i)
	{
		for (int j = 0; j < n; ++j)
		{
			if (_edges[i][j] != W_MAX)
			{
				dist[i][j] = _edges[i][j];
				prev[i][j] = i;
			}
			else if (i == j)
			{
				dist[i][j] = 0;
			}
		}
	}

	//通过迭代优化,将三维动态规划转为二维
	for (int k = 0; k < n; ++k)
	{
		for (int i = 0; i < n; ++i)
		{
			for (int 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];
					prev[i][j] = prev[k][j];
				}
			}
		}
	}
}

五、Dijkstra、Bellman-Ford和Floyd-Warshall的对比

5.1 适用场景

  • Dijkstra

    • 用途:单源最短路径算法,适用于无负权边的图
    • 应用场景:适用于网络路由、地图导航等无负权图中的路径计算。
  • Bellman-Ford

    • 用途:单源最短路径算法,适用于有负权边的图
    • 应用场景:适用于可能包含负权边的图,如金融系统中带有成本和收益的网络建模,或负值边表示优惠或折扣的情况。
  • Floyd-Warshall

    • 用途:多源最短路径算法,计算任意两点之间的最短路径
    • 应用场景:适用于所有点对的路径问题,如社交网络中两个人之间最短交友路径的计算、全连接网络拓扑分析等。

5.2 时间复杂度

  • Dijkstra

    • 使用二叉堆实现的优先队列:O(E + V log V)(其中 E 是边数,V 是顶点数)。
    • 适合稠密图。
  • Bellman-Ford

    • 时间复杂度:O(VE)。
    • 由于每次松弛需要遍历所有边,因此在稀疏图中较为高效。
  • Floyd-Warshall

    • 时间复杂度:O(V^3)。
    • 适合小型图或稠密图,节点较少但需要计算所有点对的最短路径。

5.3 松弛次序

  • Dijkstra

    • 贪心策略:每次选取当前未处理的距离最小的节点进行松弛。
    • 松弛顺序:先处理距离已知最短的节点,再依次更新它的邻居节点的距离,一旦处理一个节点,它的最短路径即确定。
  • Bellman-Ford

    • 全局松弛策略:松弛所有边 V-1 次。
    • 松弛顺序:每一轮松弛所有边,通过反复更新保证所有节点距离都正确。允许负权边。
  • Floyd-Warshall

    • 基于中间节点的松弛策略:通过每个节点 k 作为中间节点,尝试更新所有点对之间的距离。
    • 松弛顺序:每次引入一个新的中间节点 k,尝试通过 i->k->j 的路径更新任意两点 (i, j) 之间的最短距离。

5.4 处理负权边与负权环

  • Dijkstra

    • 不支持负权边:如果图中存在负权边,Dijkstra 的贪心策略将导致错误的结果。
    • 原因:一旦某个节点的最短路径被确定,Dijkstra 不会再更新这个节点,但负权边可能在后续引入更短的路径。
  • Bellman-Ford

    • 支持负权边,可以正确计算负权边的最短路径。
    • 负权环检测:算法通过 V-1 次松弛操作后检查是否仍有边可以被松弛。如果存在,则说明图中有负权环,报告错误。
  • Floyd-Warshall

    • 支持负权边,可以计算负权边的最短路径。
    • 负权环检测:通过检查矩阵的对角线元素,如果某个顶点的距离对角元素被更新为负值,则说明图中存在负权环。

5.5 总结表格

算法适用场景时间复杂度松弛次序负权边支持负权环检测
Dijkstra单源最短路径,适用于无负权图O(E + Vlog V)贪心选择距离最短的节点,逐步松弛不支持不支持
Bellman-Ford单源最短路径,适用于有负权边的图O(VE)反复松弛所有边 V-1 次支持支持
Floyd-Warshall多源最短路径,适用于小图或全图求解O(V^3)通过中间节点逐步松弛所有点对支持支持

5.6 选择算法的依据

  • 如果需要从单个源节点计算最短路径,并且图中没有负权边,Dijkstra 是最优选择,因其效率较高。
  • 如果图中有负权边,并且需要从单个源节点计算最短路径,则应使用 Bellman-Ford,它能够处理负权边并检测负权环。
  • 如果需要计算图中所有点对的最短路径(例如全连接图),Floyd-Warshall 是合适的选择,尽管它的时间复杂度较高,但处理小规模图时效果较好。

真诚点赞,手有余香

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

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

相关文章

操作教程|基于DataEase用RFM分析法分析零售交易数据

DataEase开源BI工具可以在店铺运营的数据分析及可视化方面提供非常大的帮助。同样&#xff0c;在用于客户评估的RFM&#xff08;即Recency、Frequency和Monetary的简称&#xff09;分析中&#xff0c;DataEase也可以发挥出积极的价值&#xff0c;通过数据可视化大屏的方式实时展…

液态神经网络 LNN

神经网络 (NN) 是 机器学习 模仿人脑结构和运算能力以从训练数据中识别模式的算法。 通过处理和传输信息的互连人工神经元网络&#xff0c;神经网络可以执行复杂的任务&#xff0c;例如 人脸识别, 自然语言理解&#xff0c;以及无需人工协助的预测分析。 尽管神经网络是一种强…

Mac电脑SourceTree git账号密码更改提示再次输入密码

前言&#xff1a; 最近小编git账号密码修改了&#xff0c;之前在sourceTree的git仓库在拉代码提交代码就会报错&#xff0c;提示因为密码导致的仓库连接失败。 解决方案 1.在mac电脑应用程序中搜索“钥匙串” 点击钥匙串访问 在钥匙串中选登录&#xff0c;在在右侧列表中找…

key形式和key/value形式二叉树

首先模拟一下key形式类 使用的结构是搜索二叉树 结点中有左孩子和右孩子 还有一个存储的值 template <class K>struct BSTnode//搜索二叉树不支持修改 中序遍历是有序的{K _key;BSTnode<K>* _left;BSTnode<K>* _right;BSTnode(const K& key):_key(key…

【C++】12.string类的使用

文章目录 1. 为什么学习string类&#xff1f;1.1 C语言中的字符串1.2 两个面试题(暂不做讲解) 2. 标准库中的string类2.1 string类(了解)2.2 auto和范围for 3. 查看技术文档4. string的访问5. 如何读取每个字符呢&#xff1f;6. auto语法糖&#xff08;C11&#xff09;7. 范围f…

spring boot 2.7整合Elasticsearch Java client + ingest attachment实现文档解析

一、软件环境 软件版本号备注Spring boot2.7.23.x版本建议使用ElasticSearch8.xElasticSearch7.17.4ElasticSearch 7.x 可使用JDK 8 ElasticSearch 8.x 要求使用JDK 11 二、安装ElasticSearch 下载地址&#xff1a;https://artifacts.elastic.co/downloads/elasticsearch/el…

网站建设中,虚拟主机的各项指标和参数

虚拟主机的各项指标和参数主要包括空间大小、并发连接数、带宽限制、流量限制、CPU限制、内存以及IO速度等。以下是对这些指标和参数的详细介绍&#xff1a; 空间大小&#xff1a;空间大小通常以MB或GB为单位&#xff0c;表示虚拟主机可以容纳的数据量。例如&#xff0c;一个1…

地级市-城市创业活跃度(每百人新创企业数)(2000-2021年)

城市创业活跃度通常指一个城市在一定时期内新创企业的数量和质量&#xff0c;它反映了城市的创业环境、创业者的积极性和创造力。根据中的研究&#xff0c;创业活跃度&#xff08;Entre_Activation&#xff09;作为反映区域层面创业活动积极程度的核心指标&#xff0c;被广泛用…

【Vue】Vue扫盲(二)指令:v-for 、v-if、v-else-if、v-else、v-show

【Vue】Vue扫盲&#xff08;一&#xff09;事件标签、事件修饰符&#xff1a;click.prevent click.stop click.stop.prevent、按键修饰符、及常用指令 文章目录 一、v-for遍历数组数组角标遍历对象&#xff1a;Key作用介绍 二、v-if、v-show基本用法&#xff1a;案例&#xff1…

【unity框架开发12】从零手搓unity存档存储数据持久化系统,实现对存档的创建,获取,保存,加载,删除,缓存,加密,支持多存档

文章目录 前言一、Unity对Json数据的操作方法一、JsonUtility方法二、Newtonsoft 二、持久化的数据路径三、数据加密/解密加密方法解密方法 四、条件编译指令限制仅在编辑器模式下进行加密/解密四、数据持久化管理器1、存档工具类2、一个存档数据3、存档系统数据类4、数据存档存…

【Photoshop——肤色变白——曲线】

1. 三通道曲线原理 在使用RGB曲线调整肤色时&#xff0c;你可以通过调整红、绿、蓝三个通道的曲线来实现黄皮肤到白皮肤的转变。 黄皮肤通常含有较多的红色和黄色。通过减少这些颜色的量&#xff0c;可以使肤色看起来更白。 具体步骤如下&#xff1a; 打开图像并创建曲线调…

几何完备的3D分子生成/优化扩散模型 GCDM-SBDD - 评测

GCDM 是一个新的 3D 分子生成扩散模型&#xff0c;与之前的 EDM 相比&#xff0c;GCDM 优化了其中的图神神经网络部分&#xff0c;使用手性敏感的 SE3 等变神经网络 GCPNET 代替了 EDM 中的 EGNN&#xff0c;让节点间消息传递、聚合根据手性不同而进行。本文对 GCDM-SBDD&#…

DMN决策引擎入门知识点

本文主要讲解Camunda是如何使用Dmn决策引擎&#xff0c;体验地址:www.jeecgflow.com Dmn决策表定义 Dmn在线设计 命中策略(Hit Policy) 策略名称策略描述Unique只有一个或者没有规则可以满足。决策表的结果包含满足规则的输出条目。如果超过一个规则满足&#xff0c;那么就违…

电脑知识:适用于 Windows 10 的 PDF 编辑器列表

PDF 是一种流行的、多功能且安全的文件格式&#xff0c;用于在线共享文档。但是&#xff0c;如果没有合适的应用程序&#xff0c;查看和编辑 PDF 文件可能会变得复杂。 幸运的是&#xff0c;有很多 PDF 编辑器可以帮助您更正重要文档上的错误、填写表格、为合同添加签名、更改…

【个人成长】编程小白如何成为大神?

1. 选择适合自己的编程语言 作为新手&#xff0c;选择一门适合自己的编程语言至关重要。不同的编程语言有不同的应用领域和特点。以下是几种适合初学者的编程语言&#xff1a; Python&#xff1a;广泛应用于数据科学、人工智能、自动化脚本和Web开发等领域。它语法简洁易懂&a…

Faker:自动化测试数据生成利器

Faker&#xff1a;自动化测试数据生成利器 前言1. 安装2. 多语言支持3. 常用方法3.1 生成姓名和地址3.2 生成电子邮件和电话号码3.3 生成日期和时间3.4 生成公司名称和职位3.5 生成文本和段落3.6 生成图片和颜色3.7 生成用户代理和浏览器信息3.8 生成文件和目录3.9 生成UUID和哈…

GPIO的原理

GPIO简介 GPIO&#xff08;General Purpose Input Output&#xff09;通用输入输出口 可配置为8种输入输出模式 引脚电平&#xff1a;0V~3.3V&#xff0c;部分引脚可容忍5V 输出模式下可控制端口输出高低电平&#xff0c;用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等 输入…