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

目录

  • 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];
				}
			}
		}
	}
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

spark学习总结

系列文章目录 第1天总结&#xff1a;spark基础学习 1- Spark基本介绍&#xff08;了解&#xff09;2- Spark入门案例&#xff08;掌握&#xff09;3- 常见面试题&#xff08;掌握&#xff09; 文章目录 系列文章目录前言一、Spark基本介绍1、Spark是什么1.1 定义1.2 Spark与M…

valgrind工具的交叉编译及使用

一 概述 valgrind是一款非常好用的工具&#xff0c;用于检测内存泄漏等&#xff0c;这里讲述如何将其交叉编译到arm开发板及如何使用 【C/C 集成内存调试、内存泄漏检测和性能分析的工具 Valgrind 】Linux 下 Valgrind 工具的全面使用指南 - 知乎 (zhihu.com) valgrind: fai…

60.指针数组和数组指针

一.指针数组 指针数组是一个数组&#xff0c;在指针数组中存放的是指针变量。 定义一个指针数组p int *p[5]; 内存模型如下&#xff1a; 指针数组的初始化 #include <stdio.h>int main(void) {int a1;int b2;int c3;int i;int *p[3] {&a,&b,&c};for(i0…

Unbounded CKKS for Bits NTT with Composite Modulus

参考文献&#xff1a; [CHKKS18] Cheon J H, Han K, Kim A, et al. Bootstrapping for approximate homomorphic encryption[C]//Advances in Cryptology–EUROCRYPT 2018: 37th Annual International Conference on the Theory and Applications of Cryptographic Techniques…

技术差异,应用场景;虚拟机可以当作云服务器吗

虚拟机和云服务器是现在市面上常见的两种计算资源提供方式&#xff0c;很多人把这两者看成可以相互转换或者替代的物品&#xff0c;实则不然&#xff0c;这两种资源提供方式有许多相似之处&#xff0c;但是也有不少区别&#xff0c;一篇文章教你识别两者的技术差异&#xff0c;…

RabbitMQ实践——交换器(Exchange)和绑定(Banding)

大纲 direct型交换器默认交换器命名交换器 fanout型交换器topic型交换器headers型交换器 RabbitMQ在概念上由三部分组成&#xff1a; 交换器&#xff08;Exchange&#xff09;&#xff1a;负责接收消息发布者发布消息的结构&#xff0c;同时它会根据“绑定关系”&#xff08;Ba…

52【场景作图】空间感

参考 场景绘制&#xff0c;画面空间感如何拉开&#xff1f;分分钟就能学会的场景优化思路更新啦&#xff01;_哔哩哔哩_bilibili https://www.bilibili.com/video/BV1pa411J7Ps/?spm_id_from333.337.search-card.all.click&vd_source20db0c4e2d303527ed13c4b9cdf698ec 1 …

生活实用口语柯桥成人外语培训机构“客服”用英文怎么说?

● 01. “客服”英语怎么说&#xff1f; ● 我们都知道“客服”就是“客户服务”&#xff0c; 所以Customer Service就是#15857575376客服的意思。 但是这里的“客服”指代的不是客服人员&#xff0c; 而是一种Service服务。 如果你想要表达客服人员可以加上具体的职位&a…

Java宝藏实验资源库(1)文件

一、实验目的 掌握文件、目录管理以及文件操作的基本方法。掌握输入输出流的基本概念和流处理类的基本结构。掌握使用文件流进行文件输入输出的基本方法。 二、实验内容、过程及结果 1.显示指定目录下的每一级文件夹中的.java文件 运行代码如下 &#xff1a; import java.io.…

[C++][数据结构][图][中][图的遍历][最小生成树]详细讲解

目录 1.图的遍历1.广度优先遍历2.深度优先遍历 2.最小生成树1.Kruskal算法2.Prim算法 1.图的遍历 给定一个图G和其中任意一个顶点 v 0 v_0 v0​&#xff0c;从 v 0 v_0 v0​出发&#xff0c;沿着图中各边访问图中的所有顶点&#xff0c;且每个顶 点仅被遍历一次 “遍历”&…

# [0619] Task01 绪论、马尔可夫过程、动态规划

easy-rl PDF版本 笔记整理 P1 - P2 joyrl 比对 补充 P1 - P3 相关 代码 整理 最新版PDF下载 地址&#xff1a;https://github.com/datawhalechina/easy-rl/releases 国内地址(推荐国内读者使用)&#xff1a; 链接: https://pan.baidu.com/s/1isqQnpVRWbb3yh83Vs0kbw 提取码: us…

lib9-03 配置基于时间的 ACL

实验&#xff1a;配置基于时间的 ACL 1、实验目的 通过本实验可以掌握定义 time-range 的方法基于时间 ACL 的配置和调试方法 2、实验拓扑 实验拓扑如下图所示。本实验要求只允许主机 PC1 在周一到周五每天的 8&#xff1a;00-17&#xff1a;00 访问路由器 R3 的Telnet 服务…

Python的三种方式显示图片

from PIL import Image import numpy as np im Image.open("img.png") #方法一&#xff1a;使用PIL库显示图片 a np.array(im) imImage.fromarray(a) im.show() import matplotlib.pyplot as plt #方法二&#xff1a;使用matplotlib库显示图片 plt.imshow(a) plt.s…

Covalent实现对1000亿笔链上交易解析,支持AI长期数据可用性

在区块链与人工智能&#xff08;AI&#xff09;交汇处&#xff0c;讨论往往集中于去中心化推理和去中心化训练等方面。然而&#xff0c;这一数据的关键组成部分却一直未得到足够的重视。一个主要问题是&#xff1a;我们如何保护 AI 模型中的数据不受偏见和操纵的影响&#xff1…

linux环境编程基础学习

Shell编程&#xff1a; 相对的chmod -x xx.sh可以移除权限 想获取变量的值要掏点dollar&#xff08;&#xff04;&#xff09; 多位的话要加个花括号 运算&#xff1a;expr 运算时左右两边必须要加空格 *号多个含义必须加转义符 双引号可以加反单&#xff0c;但是发过来就不行 …

企业微信,机器人定时提醒

场景&#xff1a; 每天定时发送文字&#xff0c;提醒群成员事情&#xff0c;可以用机器人代替 人工提醒。 1&#xff09;在企业微信&#xff0c;创建机器人 2&#xff09;在腾讯轻联&#xff0c;创建流程&#xff0c;选择定时任务&#xff0c;执行操作&#xff08;企业微信机…

秋招突击——6/19——新作{括号生成、合并K个排序链表}

文章目录 引言新作括号生成个人实现实现时遇到的问题实现代码 参考思路实现代码 合并K个有序链表个人实现实现代码 参考实现实现代码 总结 引言 今天把第二篇论文投了&#xff0c;后续有审稿意见再说&#xff0c;然后在进行修改的。后续的生活要步入正轨了&#xff0c;每天刷题…

FreeRTOS源码分析

目录 1、FreeRTOS目录结构 2、核心文件 3、移植时涉及的文件 4、头文件相关 4.1 头文件目录 4.2 头文件 5、内存管理 6、入口函数 7、数据类型和编程规范 7.1 数据类型 7.2 变量名 7.3 函数名 7.4 宏的名 1、FreeRTOS目录结构 使用 STM32CubeMX 创建的 FreeRTOS 工…

Ubuntu服务器搭建Git远程仓库

本文所述方法适用于小型团队在局域网环境中使用Git进行代码版本管理。 1. 安装Git 打开终端(Ctrl + Alt + T) ,输入以下命令: sudo apt update #更新软件包列表信息 sudo apt install git #安装Git 验证Git是否安装成功,可以查看Git版本: git --version 也需…

shell中的流程控制

条件判断在流程控制中的重要性 有了条件判断才能进行if判断即分支流程&#xff0c;才能进行case的多分支流程&#xff0c;才能进行for循环和while循环。 单分支流程判断 如上图所示&#xff0c;在shell编程中常使用英文状态下的分号来在Linux控制台一次性执行多条命令&#x…