【高阶数据结构(三)】图的遍历最小生成树问题

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:高阶数据结构专栏⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学习更多Go语言知识
  🔝🔝


在这里插入图片描述

高阶数据结构

  • 1. 前言
  • 2. 图的遍历
  • 3. 图的广度优先遍历
  • 4. 图的深度优先遍历
  • 5. 图的最小生成树
  • 6. Kruskal算法讲解
  • 7. prim算法讲解
  • 8. 总结以及拓展

1. 前言

如果你还不知道什么是图论,以及关于图的存储结构和一些专有名词, 请先阅读这篇文章: 初识图论.

本章重点:

本篇文章着重讲解图的两种遍历方式: 深度优先遍历和广度优先遍历. 并会模拟实现这两种遍历方法. 其次,会讲解关于图的最小生成树的概念,以及关于最小生成树的两个算法: Kruskal算法和Prim算法


2. 图的遍历

给定一个图G和其中任意一个顶点v0,从v0出发,沿着图中各边访问图中的所有顶点,且每个顶点仅被遍历一次。

在这里插入图片描述

这道面试题就是典型的图论的遍历


3. 图的广度优先遍历

正如其名, 广度优先遍历就是先遍历完一个顶点的所有相邻顶点

在这里插入图片描述

比如现在要找东西,假设有三个抽屉,东西在那个抽屉不清楚,现在要将其找到,广度优先遍历的做法是:

  1. 先将三个抽屉打开,在最外层找一遍
  2. 将每个抽屉中红色的盒子打开,再找一遍
  3. 将红色盒子中绿色盒子打开,再找一遍直到找完所有的盒子

注意:每个盒子只能找一次,不能重复找

具体到一个图中就是这样的:

在这里插入图片描述

广度优先遍历看似很简单, 对于顶点A来说, 先走B,C,D. 然后再看B顶点, 走A,C,E, 但是A和C已经走过了,所以不能再此将它们算进去. 做法很简单, 使用一个队列和一个数组, 数组用来存储一个顶点是否已经入过队列了. 对于队列而言, 它的功能相信我不说大家也能明白: 最开始只有A在队列中, A出队, BCD入队, B出队, AC不用入队, 只入E. C出队…

话不多说,直接上代码:

void BFS(const V& src) //图的广度优先遍历
{size_t srci = _index[src];//找到值src对应在数组中的下标queue<int> q;vector<bool> check(_vertex.size(), false); //用来标记哪些元素已经入过队列了q.push(srci);check[srci] = true;while (!q.empty()){int front = q.front();cout << front << ": " << _vertex[front] << endl;q.pop();//把这个顶点的朋友带入队列,并更新check数组for (int i = 0; i < _vertex.size(); i++){//_edge[front][i]代表front->i是否有边,数组值不等于MAX_W就代表它们之间直接相连if (_edge[front][i] != MAX_W && check[i] == false){check[i] = true;q.push(i);}}}
}

注意,此函数是在graph类中实现的成员函数, 其中使用到了成员变量


4. 图的深度优先遍历

正如其名, 深度优先遍历就是先一条路走到底

在这里插入图片描述
比如现在要找东西,假设有三个抽屉,东西在那个抽屉不清楚,现在要将其找到,广度优先遍历的做法是:

  1. 先将第一个抽屉打开,在最外层找一遍
  2. 将第一个抽屉中红盒子打开,在红盒子中找一遍
  3. 将红盒子中绿盒子打开,在绿盒子中找一遍
  4. 递归查找剩余的两个盒子

深度优先遍历:将一个抽屉一次性遍历完(包括该抽屉中包含的小盒子),再去递归遍历其他盒子

具体到一个图中就是这样:

在这里插入图片描述

如果之前学习过递归,回溯算法的同学,相信深度优先遍历对你来说也不是什么难题, 下面就直接上手写代码了!

void DFS(const V& src)//图的深度优先遍历
{size_t srci = _index[src];vector<bool> check(_vertex.size(), false);_DFS(srci, check);
}
void _DFS(size_t srci, vector<bool>& check)
{cout << srci << ": " << _vertex[srci] << endl;check[srci] = true;for (int i = 0; i < _vertex.size(); i++)//遍历与此点相邻的所有点if (_edge[srci][i] != MAX_W && check[i] == false)_DFS(_vertex[i], check);
}

5. 图的最小生成树

若连通图由n个顶点组成,则其生成树必含n个顶点和n-1条边。因此构造最小生成树的准则有三条:

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

最小生成树其实就是子图是最简单的连通图, n-1条边刚好可以连接n个顶点

一个图

每一个图的最小生成树是不唯一的. 一般通过Kruskal算法和prim算法来构造最小生成树


6. Kruskal算法讲解

首先构造一个由这n个顶点组成、不含任何边的图G={V,NULL},其中每个顶点自成一个连通分量,其次不断从E中取出权值最小的一条边(若有多条任取其一),若该边的两个顶点来自不同的连通分量,则将此边加入到G中。如此重复,直到所有顶点在同一个连通分量上为止。

核心:每次迭代时,选出一条具有最小权值,且两端点不在同一连通分量上的边,加入生成树

在这里插入图片描述

克鲁斯卡尔算法的核心就是每次都找最小的权值边, 只要这次选的边没有构成环, 就接着往下走. 比如此图中先选1,再选2,再选2,再选4,依次向后选. 可是问题是, 我怎么知道我选出来的边是否成环了. 这里就需要使用到之前的数据结构: 并查集. 即如果一条边关联的两个点在同一集合, 那么他们就成环了, 反之则不成环. 还有一点, 怎样知道图中哪条边的权值最小? 这里可以使用优先级队列

代码案例:

typedef Graph<V, W, MAX_W, Direction> Self;
struct EDGE //仿函数用于优先级队列
{int _srci;int _desti;W _w;EDGE(int srci, int desti, W w) :_srci(srci), _desti(desti), _w(w){}bool operator>(const EDGE& e) const{return _w > e._w;}
};
W Kruskal(Self& mintree)克鲁斯卡尔算法,返回权重总和
{int n = _vertex.size();mintree._vertex = _vertex;//最开始传入的最小生成树是没有初始化的,没有点和边的关系,若直接使用add会报错mintree._edge.resize(n);mintree._index = _index;for (int i = 0; i < n; i++)mintree._edge[i].resize(n, MAX_W);priority_queue<EDGE, vector<EDGE>, greater<EDGE>> minqueue;//利用优先级队列来存储边的权重大小for (int i = 0; i < n; i++)for (int j = 0; j < n; j++)if (i < j && _edge[i][j] != MAX_W)//i<j,只用走一半,不然如果是有向图,可能会重复插入边minqueue.push(EDGE(i, j, _edge[i][j]));//将所有的边都插入优先级队列int minsize = 0;//选出n-1条边UnionFindSet ufs(n);//利用并查集来判断生成树中是否有环W totalvalue = W();//最后的返回结果while (!minqueue.empty()){EDGE min = minqueue.top();minqueue.pop();if (!ufs.SameSet(min._srci, min._desti))//如果这条边关联的两个点不在一个集合,再继续下面的操作{cout << _vertex[min._srci] << "->" << _vertex[min._desti] << ":" << min._w << endl;mintree._AddEdge(min._srci, min._desti, min._w);//增加一条边ufs.Union(min._srci, min._desti);//将这条边的两个顶点加入同一集合minsize++;//边数一直要到n-1totalvalue += min._w;//最后的结果也需要更新}}if (minsize == n - 1) return totalvalue;else return W();
}

可能大家是第一次见这样写函数, 传入一个空的图, 由于图论的复杂性和算法的特别,所以使用这种方式已经是很优解了,关于代码的解释都在注释中,若还有不懂,欢迎私信


7. prim算法讲解

prim算法的思想是,既然最小生成树包含所有的顶点. 所以我可以从任意顶点开始, 一直沿着此点向外做拓展,直到覆盖了图中所有的顶点. 算法每一步在连接集合A和A之外的结点的边中, 选择一条权值最小的加入A集合, 下图是步骤图,可以参考一下:

在这里插入图片描述

可以看见算法的每一步都在选择,当前集合A中的边的最小值,并且不会走让图成环的路径. 这个算法的实现呢,首先我们需要两个集合, A:已被选择过的点. B: 未被选择过的点. 要做的就是在已被选择的点中找权值最小,且还没选择过的点的边. 这里需要利用优先级队列,将与A集合中顶点相连的边都加入优先级队列中. 在添加边关系前,只需要判断这条边的两个顶点是否都在A集合, 如果都在,相连后就会成环. 需要pop掉这条边

直接上代码:

W Prim(Self& mintree,const V& src)//普里姆算法,返回权重总和
{size_t srci = GetIndex(src);//找到最初点的下标int n = _vertex.size();mintree._vertex = _vertex;//最开始传入的最小生成树是没有初始化的,没有点和边的关系,若直接使用add会报错mintree._edge.resize(n);mintree._index = _index;for (int i = 0; i < n; i++)mintree._edge[i].resize(n, MAX_W);unordered_set<int> x;//已被选过的顶点集合unordered_set<int> y;//未被选过的顶点集合x.insert(srci);for (int i = 0; i < n; i++)if (i != srci)y.insert(i);//从X集合中找到权值最小,并且在Y集合中的边priority_queue<EDGE, vector<EDGE>, greater<EDGE>> minqueue;//利用优先级队列,将与X集合中相关联的边都放入优先级队列,在添加边时只需要判断这条边的两个顶点是否都在X集合,若都在X集合,添加后会形成环,不可取for (size_t i = 0; i < n; i++)if (_edge[srci][i] != MAX_W)minqueue.push(EDGE(srci, i, _edge[srci][i]));//先把srci连接的边添加到队列当中W totalvalue = W();int size = 0;while (!minqueue.empty()){while (!minqueue.empty() && x.find(minqueue.top()._srci) != x.end() && x.find(minqueue.top()._desti) != x.end())//把不符合要求的边给去掉minqueue.pop();EDGE min = minqueue.top();minqueue.pop();mintree._AddEdge(min._srci, min._desti, min._w);size++;x.insert(min._desti);y.erase(min._desti);totalvalue += min._w;if (size == n - 1) break;for (int i = 0; i < n; i++)if (x.find(i) == x.end() && _edge[min._desti][i] != MAX_W)minqueue.push(EDGE(min._desti, i, _edge[min._desti][i]));//将与dest连接的所有的边都添加到优先级队列}return totalvalue;
}

若有不懂,欢迎私信


8. 总结以及拓展

关于图的最小生成树的两个算法其实都是使用的贪心策略,用局部最优找到全局最优. 但是关于这两个算法的正确性的验证这里由于篇幅有限就不多讲解了


🔎 下期预告:最短路径问题 🔍

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

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

相关文章

成功案例(IF=7.4)| 脂代谢组学和蛋白质组学分析揭示多囊卵巢综合征的发病机制

研究背景 多囊卵巢综合征&#xff08;PCOS&#xff09;是女性最常见的内分泌和代谢紊乱&#xff0c;也是无排卵性不孕症和高雄激素血症的主要原因。患者的主要临床表现为月经少、不孕、高雄激素血症、肥胖、多毛、痤疮、胰岛素抵抗&#xff08;IR&#xff09;和B超下多囊卵巢改…

Electron学习笔记(六)

文章目录 相关笔记笔记说明 七、系统5、托盘图标(1)、设置托盘图标(2)、托盘图标闪烁(3)、托盘图标菜单 6、剪切板(1)、写入剪切板(2)、读取剪切板 7、系统通知8、其他(1)、使用系统默认应用打开文件(2)、接收拖拽到窗口中的文件(3)、使用系统字体 相关笔记 Electron学习笔记&…

具身智能论文(二)

目录 1. Code as Policies: Language Model Programs for Embodied Control2. Embodied Agents for Efficient Exploration and Smart Scene Description3. Embodied Agents for Efficient Exploration and Smart Scene Description4. Learning to explore informative traject…

Stateflow基础知识笔记

01--Simulink/Stateflow概述 Stateflow是集成于Simulink中的图形化设计与开发工具&#xff0c;主要 用于针对控制系统中的复杂控制逻辑进行建模与仿真&#xff0c;或者说&#xff0c; Stateflow适用于针对事件响应系统进行建模与仿真。 Stateflow必须与Simulink联合使用&#…

第七届世界通信工程研讨会(WSCE 2024)即将召开!

第七届世界通信工程研讨会&#xff08;WSCE 2024&#xff09;将于2024年9月27-29日在日本东京举行。WSCE 的成立旨在应对通信工程领域所面临的挑战和机遇&#xff0c;尽管该领域已趋于饱和&#xff0c;但其仍保持着强劲的发展势头。本次研讨会旨在加速通信创新并加强该领域专家…

编程技巧:什么是JavaScript递归

什么是递归 程序调用自身的编程技巧称为递归&#xff08;recursion&#xff09; 递归的基本思想是将一个复杂的问题分解成更小、更易于管理的子问题&#xff0c;这些子问题与原始问题相似&#xff0c;但规模更小。 递归的要素 基本情况&#xff08;Base Case&#xff09;&…

Python 编程语言中的 None 到底是什么?

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 让我们一起深入了解 Python 中的 None。 什么是 None&#xff1f; 在 Python 编程语言中&#xff0c;None 是一个特殊的常量&#xff0c;它代表了 “无” 或 “没有值”。你可以把它想象成一个空盒子…

Debian常用命令:高效管理与运维的必备指南

在Linux世界中&#xff0c;Debian以其稳定性、安全性和开源精神赢得了广大用户的青睐。作为一个基于Linux的操作系统&#xff0c;Debian拥有丰富且强大的命令行工具&#xff0c;这些命令对于系统管理员和开发者来说至关重要。本文将为您介绍一系列Debian系统中的常用命令&#…

python 自定义包的实现

1. 代码目录 创建自定义包的时候&#xff0c;原理是当 python 检测到一个目录下存在 __init__.py 文件时&#xff0c;python 就会把它当成一个模块(module)。 下面这个例子是网上整理的代码&#xff0c;但是有些小改动&#xff0c;可以直接拿来就用。 看代码结构&#xff1a;…

flink尚硅谷

flink 1 flink基础使用1.1 角色1.2 部署模式&#xff08;抽象&#xff09;1.2.1 会话模式1.2.2 单作业模式1.2.3 应用模式 1.3 运行模式&#xff08;实际 谁来管理资源&#xff09;1.3.1 Stand alone1.3.2 YARN运行模式&#xff08;重点&#xff09; 2. 运行时架构2.1 系统架构…

【Java EE】网络原理——TCP1

目录 1.TCP协议格式 2.TCP协议的特点 3.TCP协议的核心机制&#xff08;十个&#xff09; 3.1确认应答机制 3.2超时重传 3.3连接管理 3.3.1三次握手基本流程&#xff1a; 3.3.2三次握手的意义或者解决的问题&#xff1a;&#xff08;面试题&#xff09; 3.3.3三次握手时…

什么是无人直播?无人直播软件带你探索全新的赚钱模式!

在当今数字化时代&#xff0c;AI技术的迅猛发展正引领着各行各业的深刻变革。其中&#xff0c;AI实景自动无人直播软件以其独特的优势&#xff0c;正成为商家们提升品牌形象、扩大市场影响力的重要工具。本文将详细介绍这款软件的功能特点及其在商业领域的应用价值。全网最新智…

RabbitMQ中间件安装

消息队列 RabbitMQ yum -y update yum -y install epel-release erlang # 安装erlang erl -version # 判断是否安装成功根据官网的的表格判断自己用哪个版本的 RabbitMQ&#xff1a;https://www.rabbitmq.com/docs/which-erlang#r16b03 [rootiZuf6hqrs5cb2ccyuc9nqvZ ~]# er…

【C++历练之路】unordered_map与unordered_set的封装实现

W...Y的主页 &#x1f60a; 代码仓库分享&#x1f495; 前言&#xff1a;我们已经认识并实现了哈希底层的逻辑&#xff0c;创建出了其开散列。现在我们要进行封装&#xff0c;类比STL中的unordered_set 与 unordered_map。 目录 1. 模拟实现 1.1 哈希表的改造 1.2 unorde…

uabntu pcl spdlog安装位置和版本查看那

查看pcl默认安装版本 pkg-config --modversion pcl_io 查看pcl路径 pkg-config --libs pcl_io

企业计算机服务器中了rmallox勒索病毒怎么破解,rmallox勒索病毒解密工具步骤

科技技术的发展&#xff0c;为企业的生产运营注入了新的活力&#xff0c;越来越多的企业利用网络走向了数字化办公模式&#xff0c;网络也极大地方便了企业的生产运营&#xff0c;大大提高了企业的生产效率&#xff0c;加快了企业发展的步伐。但是网络数据安全问题一直是企业关…

Swift 集合类型

集合类型 一、集合的可变性二、数组&#xff08;Arrays&#xff09;1、数组的简单语法2、创建一个空数组3、创建一个带有默认值的数组4、通过两个数组相加创建一个数组5、用数组字面量构造数组6、访问和修改数组7、数组的遍历 三、集合&#xff08;Sets&#xff09;1、集合类型…

某攻防演练心得之随笔记

最近太忙了&#xff0c;忙于各种奇奇怪怪的事情&#xff0c;有攻防&#xff0c;有应急&#xff0c;有渗透&#xff0c;还成为了一段时间内的“word高级工程师”......有师傅说我现在更新的越来越慢了&#xff0c;是呀&#xff0c;其实我也不知道怎么了&#xff0c;每天各种新闻…

科林Linux_4 信号

#include <signal.h> 信号signal&#xff1a;Linux或Unix系统支持的经典的消息机制&#xff0c;用于处置进程&#xff0c;挂起进程或杀死进程 kill -l #查看系统支持的信号 1~31 Unix经典信号&#xff08;软件开发工程师&#xff09; 32、33信号被系统隐藏&#xf…

加入全球少儿编程运动:Scratch让每个孩子都能成为创造者(Scratch最新版客户端和初/中/高级学习资料整理分享)

文章目录 &#x1f4d6; 介绍 &#x1f4d6;&#x1f3e1; 演示环境 &#x1f3e1;&#x1f4d2; 文章内容 &#x1f4d2;&#x1f4dd; 安装与使用&#x1f4dd; 社区与资源 &#x1f388; 获取方式 &#x1f388;⚓️ 相关链接 ⚓️ &#x1f4d6; 介绍 &#x1f4d6; 你知道…