[C++][数据结构][图][下][最短路径]详细讲解

news2025/2/27 23:12:19

目录

  • 1.最短路径
    • 1.单源最短路径 -- Dijkstra算法
    • 2.单源最短路径 -- Bellman-Ford算法
    • 3.多源最短路径 -- Floyd-Warshall算法
      • 原理


1.最短路径

  • 最短路径问题:从在带权有向图G中的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小

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

  • 时间复杂度: O ( N 2 ) O(N^2) O(N2),空间复杂度: O ( N ) O(N) O(N)

  • **单源最短路径问题:**给定一个图 G = ( V , E ) G = ( V , E ) G=(V,E),求源结点 s ∈ V s ∈ V sV到图中每个结点 v ∈ V v ∈ V vV的最短路径

    • Dijkstra算法就适用于解决带权重的有向图上的单源最短路径问题
    • 同时算法要求图中所有边的权重非负
    • 一般在求解最短路径的时候都是已知一个起点和一个终点,所以使用Dijkstra算法求解过后也就得到了所需起点到终点的最短路径
  • Dijkstra算法每次都是选择V-S中最小的路径节点来进行更新,并加入S中,所以该算法使用的是贪心策略

    • Dijkstra去找最小起始边
    • Dijkstra的贪心策略:每次去选从s -> #,最短路径边的那个顶点,去更新其连接的路径,u是不是在S中的点
  • 针对一个带权有向图G,将所有结点分为两组S和Q

    • S是已经确定最短路径的结点集合,在初始时为空
      • 初始时就可以将源节点s放入,毕竟源节点到自己的代价是0)
    • Q为其余未确定最短路径的结点集合,每次从Q中找出一个起点到该结点代价最小的结点u,将u从Q中移出,并放入S中,对u的每一个相邻结点v进行松弛操作
    • 如此一直循环直至集合Q为空
      • 即:所有节点都已经查找过一遍并确定了最短路径,至于一些起点到达不了的结点在算法循环后其代价仍为初始设定的值,不发生变化
  • 松弛

    • 对每一个相邻结点v ,判断源节点s到结点u的代价与u到v的代价之和是否比原来s到v的代价更小
      • srci->u + u->v < srci->v
    • 若代价比原来小,则要将s到v的代价更新为s到u与u到v的代价之和,否则维持原样
  • Dijkstra算法存在的问题不支持图中带负权路径

    • 如果带有负权路径,则可能会找不到一些路径的最短路径
  • 在下图Dijkstra算法的执行过程

    • 源节点s为最左边的结点
    • 每个结点中的数值为该结点的最短路径的估计值
    • 加了阴影的边表明前驱值
    • 黑色的结点属于集合S
      请添加图片描述
// 比较抽象,对着图和文档会好理解些:P
void Dijkstra(const V& src, vector<W>& dist, vector<int>& pPath)
{
	size_t srci = GetVertexIndex(src);
	size_t n = _vertexs.size();

	dist.resize(n, MAX_W); // dist[] 存储src -> *的最短路径
	pPath.resize(n, -1); // pPath 存储路径前一个顶点下标

	dist[srci] = 0;
	pPath[srci] = srci;

	// 已经确定最短路径的顶点集合
	vector<bool> S(n, false);

	for (size_t i = 0; i < n; i++) // n个顶点
	{
		// 选最短路径且不在S的顶点,更新其他路径
		int u = 0;
		W min = MAX_W;
		for (size_t j = 0; j < n; j++)
		{
			if (S[j] == false && dist[j] < min)
			{
				u = j;
				min = dist[j];
			}
		}
		S[u] = true;

		// 松弛更新u连接顶点v  srci->u + u->v  <  srci->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[v] = u;
			}
		}
	}
}

void PrintShortPath(const V& src, const vector<W>& dist, const vector<int>& pPath)
{
	size_t srci = GetVertexIndex(src);
	size_t n = _vertexs.size();

	for (size_t i = 0; i < n; i++)
	{
		if (i != srci)
		{
			// 找出i顶点的路径
			vector<int> path;
			size_t parenti = i;

			while (parenti != srci)
			{
				path.push_back(parenti);
				parenti = pPath[parenti];
			}
			path.push_back(srci);
			reverse(path.begin(), path.end());

			for(auto index : path)
			{
				cout << _vertexs[index] << "->";
			}
			cout << dist[i] << endl;
		}
	}
}

2.单源最短路径 – Bellman-Ford算法

  • 时间复杂度:O(N^3) 空间复杂度:O(N)
  • Dijkstra算法只能用来解决正权图的单源最短路径问题,但有些题目会出现负权图,此时这个算法就不能帮助我们解决问题了,而Bellman-Ford算法可以解决负权图的单源最短路径问题
  • Bellman-Ford找终止边,是个暴力求解算法
  • 优点:
    • 可以解决有负权边的单源最短路径问题
    • 可以用来判断是否有负权回路
  • 缺点时间复杂度: O ( N ∗ E ) O(N*E) O(NE)
    • N是点数,E是边数
    • 普遍是要高于Dijkstra算法O(N²)的
    • 此处如果使用邻接矩阵实现,那么遍历所有边的数量的时间复杂度就是 O ( N 3 ) O(N^3) O(N3)
    • 这里也可以看出来Bellman-Ford就是一种暴力求解更新
  • **思路梳理:**可能权值和路径对不上
    • 只要更新出了一条更短路径,可能就会影响其他路径
    • 再更新一次就修正了,但是新更新的路径又可能会影响其他路径
    • 所以还要继续更新,最多更新n轮
  • 优化:
    • 循环提前跳出
    • 队列优化 – SPFA
      • 所有边入队列
      • 后面的轮次:更新最短路径的边入队列
  • 注意:负权回路,神仙难救,求不出最短路径
  • 在下图执行Bellman-Ford算法过程
    • 源节点为s,结点中的数值为该节点的distance值
    • 加了阴影的边表示前驱值
    • 如果边 ( u , v ) (u, v) (u,v)加了阴影,则 v . Π = u v.Π = u v=u
    • 在本图例子中,每一次松弛操作对边的处理次序都是:
      • ( t , x ) , ( t , y ) , ( t , x ) , ( y , x ) , ( y , z ) , ( z , x ) , ( z , s ) , ( s , t ) , ( s , y ) (t, x),(t, y),(t, x),(y, x),(y, z),(z, x),(z, s),(s, t),(s, y) (t,x)(t,y)(t,x)(y,x)(y,z)(z,x)(z,s)(s,t)(s,y)
    • (a)在第一次松弛操作前的场景,(b)~(e)在对边进行每次松弛操作后的场景,(e)中的d值和 Π Π Π值为最终取值
    • 在本例中,Bellman-Ford算法返回值为TRUE
      请添加图片描述
bool BellmanFord(const V& src, vector<W>& dist, vector<int>& pPath)
{
    size_t n = _vertexs.size();
    size_t srci = GetVertexIndex(src);

    dist.resize(n, MAX_W);
    pPath.resize(n, -1);

    // 先更新srci->srci为缺省值
    dist[srci] = W();

    // 总体最多更新n轮
    for (size_t k = 0; k < n; k++)
    {
        bool update = false;

        // i->j松弛
        for (size_t i = 0; i < n; i++)
        {
            for (size_t j = 0; j < n; j++)
            {
                // srci -> i + i -> j
                if (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j])
                {
                    dist[j] = dist[i] + _matrix[i][j];
                    pPath[j] = i;

                    update = true;

                    cout << _vertexs[i] << "->" << _vertexs[j] << ":" << _matrix[i][j] << endl;
                }
            }
        }

        // 如果这个伦茨中没有更新出更短路径,那么后续轮次就不需要走了
        if (!update)
        {
            break;
        }
    }

    // 还能更新,则为带负权回路
    for (size_t i = 0; i < n; i++)
    {
        for (size_t j = 0; j < n; j++)
        {
            // srci -> i + i -> j
            if (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j])
            {
                return false;
            }
        }
    }

    return true;
}

3.多源最短路径 – Floyd-Warshall算法

  • Floyd-Warshall算法是解决任意两点间的最短路径的一种算法
    • 以所有点为源点,求出任意两点之间的距离
  • Floyd-Warshall算法考虑的是一条最短路径的中间节点
    • 即:简单路径 p = { v 1 , v 2 , … , v n } p=\{v1,v2,…,vn\} p={v1,v2,,vn}上除 v 1 v_1 v1 v n v_n vn的任意节点
  • 设k是p的一个中间节点,那么从 i − > j i->j i>j的最短路径p就被分成 i − > k i->k i>k k − > j k->j k>j的两段最短路径 p 1 p_1 p1 p 2 p_2 p2
    • p 1 p_1 p1是从 i − > k i->k i>k且中间节点属于 { 1 , 2 , … , k − 1 } \{1,2,…,k-1\} {12k1}取得的一条最短路径
    • p 2 p_2 p2是从 k − > j k->j k>j且中间节点属于 { 1 , 2 , … , k − 1 } \{1, 2,…,k-1\} {12k1}取得的一条最短路径
  • 下图中
    • 路径p是 i − > j i->j i>j的一条最短路径,结点k是路径p上编号最大的中间节点
    • 路径 p 1 p_1 p1是路径p上 i − > k i->k i>k之间的一段,其所有中间节点取自集合 { 1 , 2 , … , k − 1 } \{1, 2,…,k-1\} {12k1}
    • k − > j k->j k>j的路径 p 2 p_2 p2也遵守同样的规则
      请添加图片描述

原理

  • Floyd-Warshall算法的原理是动态规划

    • 在实际算法中,为了节约空间,可以直接在原来的空间上进行迭代,这样空间可降至二维

      请添加图片描述

  • 即:Floyd算法本质是三维动态规划 D [ i ] [ j ] [ k ] D[i][j][k] D[i][j][k]表示 i − > j i->j i>j只经过0到k个点最短路径,然后建立起转移方程,然后通过空间优化,优化掉最后一维度,变成一个最短路径的迭代算法,最后即得到所以点的最短路径
    请添加图片描述

    请添加图片描述

void FloydWarshall(vector<vector<W>>& dist, vector<vector<int>>& pPath)
{
	size_t n = _vertexs.size();
	dist.resize(n);
	pPath.resize(n);

	// 初始化权值和路径矩阵
	for (size_t i = 0; i < n; i++)
	{
		dist[i].resize(n, MAX_W);
		pPath[i].resize(n, -1);
	}

	// 首先直接相连的边更新
	for (size_t i = 0; i < n; i++)
	{
		for (size_t j = 0; j < n; j++)
		{
			if (_matrix[i][j] != MAX_W)
			{
				dist[i][j] = _matrix[i][j];
				pPath[i][j] = i;
			}

			if (i == j)
			{
				dist[i][j] = W();
			}
		}
	}

	// 最短路径的更新 i -> {else} -> j
	for (size_t k = 0; k < n; k++)
	{
		for (size_t i = 0; i < n; i++)
		{
			for (size_t j = 0; j < n; j++)
			{
				// k作为i j的中间点,尝试去更新i->j的路径
				if (dist[i][k] != MAX_W && dist[k][j] != MAX_W
					&& dist[i][k] + dist[k][j] < dist[i][j])
				{
					dist[i][j] = dist[i][k] + dist[k][j];

					// 找跟j相连的上一个邻接顶点,k j不一定是直接相连的
					// 如果k->j 直接相连,pPath[k][j]存的就是k
					// 如果k->j 没有直接相连,k->...->x->j,pPath[k][j]村的就是x
					pPath[i][j] = pPath[k][j];
				}
			}
		}
	}
}

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

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

相关文章

linux中“PXE高效批量装机”

在大规模的 Linux 应用环境中&#xff0c;如 Web 群集、分布式计算等&#xff0c;服务器往往并不配备光驱设备&#xff0c;在这种情况下&#xff0c;如何为数十乃至上百台服务器裸机快速安装系统呢&#xff1f;传统的 USB光驱、移动硬盘等安装方法显然已经难以满足需求。 PXE …

Javase.抽象类和接口

抽象类和接口 【本节目标】1.抽象类1.1抽象类的概念1.2 抽象类语法1.3 抽象类特性1.4 抽象类的作用 2. 接口2.1 接口的概念2.2 语法规则2.3 接口使用2.4 接口特性2.5 实现多个接口2.6 接口间的继承2.7 接口使用实例2.8Clonable 接口和深拷贝2.9 抽象类和接口的区别 3. Object类…

C#的Switch语句2(case后的值与模式匹配)

文章目录 switch语法结构case具体的值枚举值字符串const关键字 如果没有匹配的值default语句不一定要在最后 模式匹配与C的差异-case穿透&#xff08;Fall-through&#xff09;下一篇文章 switch语法结构 基础的语法结构&#xff0c;在上一篇文章已经写了&#xff0c;具体请看…

Pyshark——安装、解析pcap文件

1、简介 PyShark是一个用于网络数据包捕获和分析的Python库&#xff0c;基于著名的网络协议分析工具Wireshark和其背后的libpcap/tshark库。它提供了一种便捷的方式来处理网络流量&#xff0c;适用于需要进行网络监控、调试和研究的场景。以下是PyShark的一些关键特性和使用方…

顺势而为:雷军、小米与创业成功的深层思考

一、引言 在当今快速发展的商业环境中&#xff0c;成功的企业家如马云和雷军&#xff0c;都以其独特的商业智慧和不懈的勤奋精神赢得了业界的尊重。然而&#xff0c;当我们深入探讨他们的成功之道时&#xff0c;会发现一个更为核心的因素——“顺势而为”。本文将基于雷军对不…

HTML静态网页成品作业(HTML+CSS+JS)——我的家乡福州介绍网页(3个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;使用Javacsript代码实现图片轮播&#xff0c;共有3个页面。 二、作品…

Linux mongodb安装及简单使用

说明&#xff1a;本文章主要是对mongodb的单击安装 1.创建文件夹&#xff0c;准备安装包 cd /user/local mkdir tools 2.解压mongodb包 mkdir mongodb tar -xvf mongodb-linux-x86_64-rhel70-5.0.11.tgz -C mongodb 3.进入解压目录 cd mongodb cd mongodb-linux-x86_64-…

spark 整合 yarn

spark 整合 yarn 1、在master节点上停止spark集群 cd /usr/local/soft/spark-2.4.5/sbin ./stop-all.sh 2、spark整合yarn只需要在一个节点整合, 可以删除node1 和node2中所有的spark文件 分别在node1、node2 的/usr/local/soft目录运行 rm -rf spark-2.4.…

千脑计划:模拟人类大脑皮层,开启AI新纪元

随着科技的飞速发展&#xff0c;人工智能已成为当今时代的热门话题。然而&#xff0c;目前主流的深度神经网络虽然取得了显著成就&#xff0c;但也面临着能耗高、稳定性差等问题。为了解决这些挑战&#xff0c;一项名为“千脑计划”的宏伟项目应运而生&#xff0c;旨在通过模仿…

Nacos配置中心不可用会有什么影响

服务端&#xff1a; Nacos的数据存储接口 com.alibaba.nacos.config.server.service.DataSourceService 有两种实现&#xff1a; 如果指定了mysq 作为数据库&#xff0c;则必须使用 mysql 如果是 集群方式部署Nacos&#xff0c;则必须使用mysql 如果是单例方式部署 并且 没…

【因果推断python】37_断点回归3

目录 羊皮效应和模糊 RDD&#xff08;Fuzzy RDD&#xff09; 麦克雷测试&#xff08;McCrary Test&#xff09; 关键思想 羊皮效应和模糊 RDD&#xff08;Fuzzy RDD&#xff09; 关于教育对收入的影响&#xff0c;经济学有两种主要观点。第一个是广为人知的论点&#xff0c;…

springboot应用cpu飙升的原因排除

1、通过top或者jps命令查到是那个java进程&#xff0c; top可以看全局那个进程耗cpu&#xff0c;而jps则默认是java最耗cpu的&#xff0c;比如找到进程是196 1.1 top (推荐)或者jps命令均可 2、根据第一步获取的进程号&#xff0c;查询进程里那个线程最占用cpu&#xff0c;发…

C++ | Leetcode C++题解之第168题Excel表列名称

题目&#xff1a; 题解&#xff1a; class Solution { public:string convertToTitle(int columnNumber) {string ans;while (columnNumber > 0) {--columnNumber;ans columnNumber % 26 A;columnNumber / 26;}reverse(ans.begin(), ans.end());return ans;} };

计算机组成原理---Cache的基本工作原理习题

对应知识点&#xff1a; Cache的基本原理 1.某存储系统中&#xff0c;主存容量是Cache容量的4096倍&#xff0c;Cache 被分为 64 个块&#xff0c;当主存地址和Cache地址采用直接映射方式时&#xff0c;地址映射表的大小应为&#xff08;&#xff09;(假设不考虑一致维护和替…

C#.Net筑基-类型系统②常见类型

01、结构体类型Struct 结构体 struct 是一种用户自定义的值类型&#xff0c;常用于定义一些简单&#xff08;轻量&#xff09;的数据结构。对于一些局部使用的数据结构&#xff0c;优先使用结构体&#xff0c;效率要高很多。 可以有构造函数&#xff0c;也可以没有。因此初始…

SpringCloud Alibaba Sentinel 流量控制之流控模式实践总结

官网文档&#xff1a;https://sentinelguard.io/zh-cn/docs/flow-control.html wiki地址&#xff1a;https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6 本文版本&#xff1a;spring-cloud-starter-alibaba&#xff1a;2.2.0.RELEASE 如下图所…

【归并排序】| 详解归并排序 力扣912

&#x1f397;️ 主页&#xff1a;小夜时雨 &#x1f397;️专栏&#xff1a;归并排序 &#x1f397;️如何活着&#xff0c;是我找寻的方向 目录 1. 题目解析2. 代码 1. 题目解析 题目链接: https://leetcode.cn/problems/sort-an-array/ 我们上道题讲过归并排序的核心代码&a…

idea 创建properties文件,解决乱码

设置properties文件编码 点击file->Settings File Encodings->设置utf-8 重新创建.properties文件才生效

MySQL的自增 ID 用完了,怎么办?

MySQL 自增 ID 一般用的数据类型是 INT 或 BIGINT&#xff0c;正常情况下这两种类型可以满足大多数应用的需求。 当然也有不正常的情况&#xff0c;当达到其最大值时&#xff0c;尝试插入新的记录会导致错误&#xff0c;错误信息类似于&#xff1a; ERROR 167 (22003): Out o…

算法期末整理(正在更新中)

一 算法概述 算法的概念 通俗地讲&#xff0c;算法是指解决问题的一种方法或一个过程。更严格地讲&#xff0c;算法是由若干条指令组成的有穷序列。 算法的性质 1.输入&#xff1a;有0个或多个由外部提供的量作为算法的输入。 2.输出&#xff1a;算法产生至少一个量作为输出。…