DS图(中)(19)

文章目录

  • 前言
  • 一、图的遍历
    • 广度优先遍历
    • 深度优先遍历
  • 二、最小生成树
    • Kruskal算法
    • Prim算法
    • 两种方法对比
  • 总结


前言

  承上启下,我们来学习下图的中篇!!!


一、图的遍历

  图的遍历指的是遍历图中的顶点,主要有 广度优先遍历 和 深度优先遍历 两种方式。

广度优先遍历

  广度优先遍历又称BFS,其遍历过程类似于二叉树的层序遍历,从起始顶点开始一层一层向外进行遍历。

在这里插入图片描述
广度优先遍历的实现:

  • 广度优先遍历需要借助一个队列和一个标记数组,利用队列先进先出的特点实现一层一层向外遍历,利用标记数组来记录各个顶点是否被访问过。
  • 刚开始时将起始顶点入队列,并将起始顶点标记为访问过,然后不断从队列中取出顶点进行访问,并判断该顶点是否有邻接顶点,如果有邻接顶点并且该邻接顶点没有被访问过,则将该邻接顶点入队列,并在入队列后立即将该邻接顶点标记为访问过。
		void BFS(const V& src)				{size_t srci = GetVertexIndex(src);// 队列和标记数组queue<int> q;vector<bool> visited(_vertexs.size(), false);q.push(srci);visited[srci] = true;int levelSize = 1;size_t n = _vertexs.size();while (!q.empty()){// 一层一层出for (int i = 0; i < levelSize; ++i){int front = q.front();q.pop();cout << front << ":" << _vertexs[front] << " ";// 把front顶点的邻接顶点入队列for (size_t i = 0; i < n; ++i){if (_matrix[front][i] != MAX_W){if (visited[i] == false){q.push(i);visited[i] = true;}}}}cout << endl;levelSize = q.size();}cout << endl;}
  • 为了防止顶点被重复加入队列导致死循环,因此需要一个标记数组,当一个顶点被访问过后就不应该再将其加入队列了。

  • 如果当一个顶点从队列中取出访问时才再将其标记为访问过,也可能会存在顶点被重复加入队列的情况,比如当图中的顶点B出队列时,顶点C作为顶点B的邻接顶点并且还没有被访问过(顶点C还在队列中),此时顶点C就会再次被加入队列,因此最好在一个顶点被入队列时就将其标记为访问过。

  • 如果所给图不是一个连通图,那么从一个顶点开始进行广度优先遍历,无法遍历完图中的所有顶点,这时可以遍历标记数组,查看哪些顶点还没有被访问过,对于没有被访问过的顶点,则从该顶点处继续进行广度优先遍历,直到图中所有的顶点都被访问过。

深度优先遍历

  深度优先遍历又称DFS,其遍历过程类似于二叉树的先序遍历,从起始顶点开始不断对顶点进行深入遍历。

在这里插入图片描述
深度优先遍历的实现:

  1. 深度优先遍历可以通过递归实现,同时也需要借助一个标记数组来记录各个顶点是否被访问过。
  2. 从起始顶点处开始进行递归遍历,在遍历过程中先对当前顶点进行访问,并将其标记为访问过,然后判断该顶点是否有邻接顶点,如果有邻接顶点并且该邻接顶点没有被访问过,则递归遍历该邻接顶点。
		void _DFS(size_t srci, vector<bool>& visited){cout << srci << ":" << _vertexs[srci] << endl;visited[srci] = true;// 找一个srci相邻的没有访问过的点,去往深度遍历for (size_t i = 0; i < _vertexs.size(); ++i){if (_matrix[srci][i] != MAX_W && visited[i] == false){_DFS(i, visited);}}}void DFS(const V& src){size_t srci = GetVertexIndex(src);vector<bool> visited(_vertexs.size(), false);_DFS(srci, visited);}
  • 如果所给图不是一个连通图,那么从一个顶点开始进行深度优先遍历,无法遍历完图中的所有顶点,这时可以遍历标记数组,查看哪些顶点还没有被访问过,对于没有被访问过的顶点,则从该顶点处继续进行深度优先遍历,直到图中所有的顶点都被访问过。

二、最小生成树

关于最小生成树

  • 一个连通图的最小连通子图称为该图的生成树,若连通图由 n 个顶点组成,则其生成树必含 n 个顶点和 n−1 条边,最小生成树指的是一个图的生成树中,总权值最小的生成树。

  • 连通图中的每一棵生成树都是原图的一个极大无环子图,从其中删去任何一条边,生成树就不再连通,在其中引入任何一条新边,都会形成一条回路。

  1. 对于各个顶点来说,除了第一个顶点之外,其他每个顶点想要连接到图中,至少需要一条边使其连接进来,所以由 n 个顶点的连通图的生成树有 n 个顶点和 n−1 条边。

  2. 对于生成树来说,图中的每个顶点已经连通了,如果再引入一条新边,那么必然会使得被新边相连的两个顶点之间存在一条直接路径和一条间接路径,即形成回路。

  3. 最小生成树是图的生成树中总权值最小的生成树,生成树是图的最小连通子图,而连通图是无向图的概念,有向图对应的是强连通图,所以最小生成树算法的处理对象都是无向图。

综上,我们可以得出以下生成 最小生成树 的准则

  1. 只能使用图中的边来构造最小生成树。
  2. 只能使用恰好 n−1 条边来连接图中的 n 个顶点。
  3. 选用的 n−1 条边不能构成回路。

构造最小生成树的算法有 Kruskal算法 和 Prim算法 ,这两个算法都采用了逐步求解的贪心策略。

Kruskal算法

Kruskal算法的基本思想如下:

  1. 构造一个含 n 个顶点、不含任何边的图作为最小生成树,对原图中的各个边按权值进行排序。
  2. 每次从原图中选出一条最小权值的边,将其加入到最小生成树中,如果加入这条边会使得最小生成树中构成回路,则重新选择一条边。
  3. 按照上述规则不断选边,当选出 n−1 条合法的边时,则说明最小生成树构造完毕,如果无法选出 n−1 条合法的边,则说明原图不存在最小生成树。

在这里插入图片描述

还记得我们之前学过的并查集么,如果忘记的话,快去复习一下吧!

  • 根据原图设置最小生成树的顶点集合,以及顶点与下标的映射关系,开辟最小生成树的邻接矩阵空间,并将矩阵中的值初始化为 MAX_W ,表示刚开始时最小生成树中不含任何边。

  • 遍历原图的邻接矩阵,按权值将原图中的所有边添加到优先级队列(小堆)中,为了避免重复添加相同的边,在遍历原图的邻接矩阵时只应该遍历矩阵的一半。

  • 使用一个并查集来辅助判环操作,刚开始时图中的顶点各自为一个集合,当两个顶点相连时将这两个顶点对应的集合进行合并,使得连通的顶点在同一个集合,这样通过并查集就能判断所选的边是否会使得最小生成树中构成回路,如果所选边连接的两个顶点本就在同一个集合,那么加入这条边就会构成回路。

  • 使用 count 和 totalWeight 分别记录所选边的数量和最小生成树的总权值,当 count 的值等于 n−1 时则停止选边,此时可以将最小生成树的总权值作为返回值进行返回。

  • 每次选边时从优先级队列中获取一个权值最小的边,并通过并查集判断这条边连接的两个顶点是否在同一个集合,如果在则重新选边,如果不在则将这条边添加到最小生成树中,并将这条边连接的两个顶点对应的集合进行合并,同时更新 count 和 totalWeight 的值。

  • 当选边结束时,如果 count 的值等于 n−1 ,则说明最小生成树构造成功,否则说明原图无法构造出最小生成树。

		typedef Graph<V, W, MAX_W, Direction> Self;struct Edge{size_t _srci;size_t _dsti;W _w;Edge(size_t srci, size_t dsti, const W& w):_srci(srci),_dsti(dsti),_w(w){}bool operator>(const Edge& e) const{return _w > e._w;}};W Kruskal(Self& minTree){size_t n = _vertexs.size();minTree._vertexs = _vertexs;minTree._indexMap = _indexMap;minTree._matrix.resize(n);for (size_t i = 0; i < n; ++i){minTree._matrix[i].resize(n, MAX_W);}priority_queue<Edge, vector<Edge>, greater<Edge>> minque;for (size_t i = 0; i < n; ++i){for (size_t j = 0; j < n; ++j){if (i < j && _matrix[i][j] != MAX_W){minque.push(Edge(i, j, _matrix[i][j]));}}}// 选出 n - 1 条边int size = 0;W totalW = W();UnionFindSet ufs(n);while (!minque.empty()){Edge min = minque.top();minque.pop();if (!ufs.InSet(min._srci, min._dsti)){//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] <<":"<<min._w << endl;minTree._AddEdge(min._srci, min._dsti, min._w);ufs.Union(min._srci, min._dsti);++size;totalW += min._w;}else{//cout << "构成环:";//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;}}if (size == n - 1){return totalW;}else{return W();}}
  • 在获取图的最小生成树时,会以无参的方式定义一个最小生成树对象,然后用原图对象调用上述 Kruskal函数 ,通过输出型参数的方式获取原图的最小生成树,由于我们定义了一个带参的构造函数,使得编译器不再生成默认构造函数,因此需要通过 default 关键字强制生成 Graph类 的默认构造函数。

  • 一条边包含两个顶点和边的权值,可以定义一个Edge结构体来描述一条边,结构体内包含边的源顶点和目标顶点的下标以及边的权值,在使用优先级队列构造小堆结构时,需要存储的对象之间能够支持 > 运算符操作,因此需要对Edge结构体的 > 运算符进行重载,将其重载为边的权值的比较。

  • 当选出的边不会构成回路时,需要将这条边插入到最小生成树对应的图中,此时已经知道了这条边的源顶点和目标顶点对应的下标,可以在 Graph类 中新增一个 _AddEdge 子函数,该函数支持通过源顶点和目标顶点的下标向图中插入边,而 Graph类 中原有的 AddEdge函数 可以复用这个 _AddEdge 子函数。

  • 最小生成树不一定是唯一的,特别是当原图中存在很多权值相等的边的时候。

  • 上述代码中通过优先级队列构造小堆来依次获取权值最小的边,你也可以通过其他排序算法按权值对边进行排序,然后按权值从小到大依次遍历各个边进行选边操作。

Prim算法

Prim算法的基本思想如下:

  1. 构造一个含 n 个顶点、不含任何边的图作为最小生成树,将图中的顶点分为两个集合,forest 集合中的顶点是已经连接到最小生成树中的顶点,remain 集合中的顶点是还没有连接到最小生成树中的顶点,刚开始时 forest 集合中只包含给定的起始顶点。

  2. 每次从连接 forest 集合与 remain 集合的所有边中选出一条权值最小的边,将其加入到最小生成树中,由于选出来的边对应的两个顶点一个属于 forest 集合,另一个属于 remain 集合,因此这种方法是天然不会构成回路的。

  3. 按照上述规则不断选边,当选出 n−1 条边时,所有的顶点都已经加入到了 forest 集合,此时最小生成树构造完毕,如果无法选出 n−1 条边,则说明原图不存在最小生成树。

在这里插入图片描述

  • 根据原图设置最小生成树的顶点集合,以及顶点与下标的映射关系,开辟最小生成树的邻接矩阵空间,并将矩阵中的值初始化为 MAX_W ,表示刚开始时最小生成树中不含任何边。

  • 使用一个 forest 数组来表示各个顶点是否在 forest 集合中,刚开始时只有起始顶点在 forest 集合中,并将所有从起始顶点连接出去的边加入优先级队列(小堆),这些边就是刚开始时连接 forest 集合与 remain 集合的边。

  • 使用 count 和 totalWeight 分别记录所选边的数量和最小生成树的总权值,当 count 的值等于 n−1 时则停止选边,此时将最小生成树的总权值作为返回值进行返回。

  • 每次选边时从优先级队列中获取一个权值最小的边,将这条边添加到最小生成树中,并将这条边的目标顶点加入 forest 集合中,同时更新 count 和 totalWeight 的值。

  • 此外,还需要将从这条边的目标顶点连接出去的边加入优先级队列,但是需要保证加入的边的目标顶点不能在 forest 集合,否则后续选出源顶点和目标顶点都在 forest 集合的边就会构成回路。

  • 需要注意的是,每次从优先级队列中选出一个权值最小的边时,还需要保证选出的这条边的目标顶点不在 forest 集合中,避免构成回路。虽然向优先级队列中加入边时保证了加入的边的目标顶点不在 forest 集合中,但经过后续不断的选边,可能会导致之前加入优先级队列中的某些边的目标顶点也被加入到了 forest 集合中。

  • 当选边结束时,如果 count 的值等于 n−1 ,则说明最小生成树构造成功,否则说明原图无法构造出最小生成树。

		W Prim(Self& minTree, const V& src){size_t srci = GetVertexIndex(src);size_t n = _vertexs.size();minTree._vertexs = _vertexs;minTree._indexMap = _indexMap;minTree._matrix.resize(n);for (size_t i = 0; i < n; ++i){minTree._matrix[i].resize(n, MAX_W);}/*set<int> X;set<int> Y;X.insert(srci);for (size_t i = 0; i < n; ++i){if (i != srci){Y.insert(i);}}*/vector<bool> X(n, false);vector<bool> Y(n, true);X[srci] = true;Y[srci] = false;// 从X->Y集合中连接的边里面选出最小的边priority_queue<Edge, vector<Edge>, greater<Edge>> minq;// 先把srci连接的边添加到队列中for (size_t i = 0; i < n; ++i){if (_matrix[srci][i] != MAX_W){minq.push(Edge(srci, i, _matrix[srci][i]));}}cout << "Prim开始选边" << endl;size_t size = 0;W totalW = W();while (!minq.empty()){Edge min = minq.top();minq.pop();// 最小边的目标点也在X集合,则构成环if (X[min._dsti]){//cout << "构成环:";//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;}else{minTree._AddEdge(min._srci, min._dsti, min._w);//cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;X[min._dsti]= true;Y[min._dsti] = false;++size;totalW += min._w;if (size == n - 1)break;for (size_t i = 0; i < n; ++i){if (_matrix[min._dsti][i] != MAX_W && Y[i]){minq.push(Edge(min._dsti, i, _matrix[min._dsti][i]));}}}	}if (size == n - 1){return totalW;}else{return W();}}

两种方法对比

  • Prim算法构造最小生成树的思想在选边时是不需要判环,但上述利用优先级队列实现的过程中仍需判环,如果在每次选边的时候能够通过某种方式,从连接 forest 集合和 remain 集合的所有边中选出权值最小的边,那么就无需判环,但这两个集合中的顶点是不断在变化的,每次选边时都遍历连接两个集合的所有边,该过程的时间复杂度较高(达到O(N^3)的级别),所以我们采用 vector< bool > 的方法来表示两个集合,进而解决了判环的问题
  • Kruskal算法本质是一种全局的贪心,每次选边时都是在所有边中选出权值最小的边,而Prim算法本质是一种局部的贪心,每次选边时是从连接 forest 集合和 remain 集合的所有边中选出权值最小的边。

总结

  困了,接下来还有最短路径问题,就等早上起来再写吧!

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

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

相关文章

DeepSeek 发布多模态 Janus-Pro

DeepSeek在接连发布大语言模型V3&#xff0c;推理模型R1之后&#xff0c;DeepSeek随后又发布两款多模态框架&#xff1a;Janus-Pro 与 JanusFlow &#xff0c;引领多模态模型新时代&#xff01; 而且依然是保持了一贯的风格&#xff0c;保持了完全开源&#xff0c;今天我们来看…

【QT笔记】使用QScrollArea实现多行文本样式显示

目录 一、QScrollArea 的基本概念 二、demo代码 三、实现效果 1、页面空间足够&#xff0c;无滚动条时显示效果 2、有滚动条时显示效果 一、QScrollArea 的基本概念 QScrollArea 是 Qt 框架中用于提供一个滚动条区域&#xff0c;允许用户滚动查看比当前可视区域更大的内容…

达梦数据库从单主模式转换为主备模式

目录标题 达梦数据库单主转主备配置笔记前期准备服务器环境数据库安装磁盘空间 流程流程图说明 详细步骤1. 检查主库归档模式2. 配置主库配置文件dm.ini 文件dmmal.ini 文件dmarch.ini 文件 3. 备份主库数据库4. 备库配置新建备库数据库配置备库配置文件dm.ini 文件复制主库的 …

使用C#开发一款通用数据库管理工具

由于经常使用各种数据库&#xff0c;笔者自己动手丰衣足食&#xff0c;使用C#开发了一款通用数据库管理工具&#xff0c;支持Mysql、Oracle、Sqlite、SQL Server等数据库的表、视图、存储过程、函数管理功能&#xff0c;并支持导入导出、数据字典生成、拖拽式跨机器跨库数据一键…

w193基于Spring Boot的秒杀系统设计与实现

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…

DeepSeek各版本说明与优缺点分析

DeepSeek各版本说明与优缺点分析 DeepSeek是最近人工智能领域备受瞩目的一个语言模型系列&#xff0c;其在不同版本的发布过程中&#xff0c;逐步加强了对多种任务的处理能力。本文将详细介绍DeepSeek的各版本&#xff0c;从版本的发布时间、特点、优势以及不足之处&#xff0…

OpenCV:特征检测总结

目录 一、什么是特征检测&#xff1f; 二、OpenCV 中的常见特征检测方法 1. Harris 角点检测 2. Shi-Tomasi 角点检测 3. Canny 边缘检测 4. SIFT&#xff08;尺度不变特征变换&#xff09; 5. ORB 三、特征检测的应用场景 1. 图像匹配 2. 运动检测 3. 自动驾驶 4.…

windows版的docker如何使用宿主机的GPU

windows版的docker使用宿主机的GPU的命令 命令如下 docker run -it --nethost --gpus all --name 容器名 -e NVIDIA_DRIVER_CAPABILITIEScompute,utility -e NVIDIA_VISIBLE_DEVICESall 镜像名效果 (transformer) rootdocker-desktop:/# python Python 3.9.0 (default, Nov 15 …

neo4j-在Linux中安装neo4j

目录 切换jdk 安装neo4j 配置neo4j以便其他电脑可以访问 切换jdk 因为我安装的jdk是1.8版本的&#xff0c;而我安装的neo4j版本为5.15,Neo4j Community 5.15.0 不支持 Java 1.8&#xff0c;它要求 Java 17 或更高版本。 所以我需要升级Java到17 安装 OpenJDK 17 sudo yu…

8.PPT:小李-第二次世界大战【21】

目录 NO123 ​ NO4567 ​ NO8\9\10\11​ 图片→格式→大小对话框→锁定纵横比✔动画→飞入→效果选项&#xff1a;方向/序列→开始→持续时间→延迟时间持续时间&#xff1a;1s延迟&#xff1a;0.5s音频剪切时间&#xff1a;0.5s&#xff1a;00:00.500自动换片时间设置&…

GAN(生成对抗网络,Generative Adversarial Network)

https://www.bilibili.com/video/BV1mp4y187dm/?spm_id_from333.788.recommend_more_video.2&vd_source35b06c13f470dff84c947fa3045bafc3

【C++】多态详细讲解

本篇来聊聊C面向对象的第三大特性-多态。 1.多态的概念 多态通俗来说就是多种形态。多态分为编译时多态(静态多态)和运⾏时多态(动态多态)。 编译时多态&#xff1a;主要就是我们前⾯讲的函数重载和函数模板&#xff0c;他们传不同类型的参数就可以调⽤不同的函数&#xff0c;通…

NeuralCF 模型:神经网络协同过滤模型

实验和完整代码 完整代码实现和jupyter运行&#xff1a;https://github.com/Myolive-Lin/RecSys--deep-learning-recommendation-system/tree/main 引言 NeuralCF 模型由新加坡国立大学研究人员于 2017 年提出&#xff0c;其核心思想在于将传统协同过滤方法与深度学习技术相结…

【自动化办公】批量图片PDF自定义指定多个区域识别重命名,批量识别铁路货物运单区域内容改名,基于WPF和飞桨ocr深度学习模型的解决方案

项目背景介绍 铁路货运企业需要对物流单进行长期存档&#xff0c;以便后续查询和审计。不同的物流单可能包含不同的关键信息&#xff0c;通过自定义指定多个区域进行识别重命名&#xff0c;可以使存档的图片文件名具有统一的规范和明确的含义。比如&#xff0c;将包含货物运单…

Qt跨屏窗口的一个Bug及解决方案

如果我们希望一个窗口覆盖用户的整个桌面&#xff0c;此时就要考虑用户有多个屏幕的场景&#xff08;此窗口要横跨多个屏幕&#xff09;&#xff0c;由于每个屏幕的分辨率和缩放比例可能是不同的&#xff0c;Qt底层在为此窗口设置缩放比例&#xff08;DevicePixelRatio&#xf…

LeetCode:63. 不同路径 II

跟着carl学算法&#xff0c;本系列博客仅做个人记录&#xff0c;建议大家都去看carl本人的博客&#xff0c;写的真的很好的&#xff01; 代码随想录 LeetCode&#xff1a;63. 不同路径 II 给定一个 m x n 的整数数组 grid。一个机器人初始位于 左上角&#xff08;即 grid[0][0]…

自定义数据集 使用paddlepaddle框架实现逻辑回归

导入必要的库 import numpy as np import paddle import paddle.nn as nn 数据准备&#xff1a; seed1 paddle.seed(seed)# 1.散点输入 定义输入数据 data [[-0.5, 7.7], [1.8, 98.5], [0.9, 57.8], [0.4, 39.2], [-1.4, -15.7], [-1.4, -37.3], [-1.8, -49.1], [1.5, 75.6…

如何利用maven更优雅的打包

最近在客户现场部署项目&#xff0c;有两套环境&#xff0c;无法连接互联网&#xff0c;两套环境之间也是完全隔离&#xff0c;于是问题就来了&#xff0c;每次都要远程到公司电脑改完代码&#xff0c;打包&#xff0c;通过网盘&#xff08;如果没有会员&#xff0c;上传下载慢…

string类OJ练习题

目录 文章目录 前言 一、反转字符串 二、反转字符串 II 三、反转字符串中的单词 III 四、验证一个字符串是否是回文 五、字符串相加&#xff08;大数加法&#xff09; 六、字符串相乘&#xff08;大数乘法&#xff09; 七、把字符串转化为整数&#xff08;atoi&#xff09; 总结…

(一)DeepSeek大模型安装部署-Ollama安装

大模型deepseek安装部署 (一)、安装ollama curl -fsSL https://ollama.com/install.sh | sh sudo systemctl start ollama sudo systemctl enable ollama sudo systemctl status ollama(二)、安装ollama遇到网络问题&#xff0c;请手动下载 ollama-linux-amd64.tgz curl -L …