数据结构-图-最小生成树问题

news2024/12/27 1:48:13

最小生成树

  • 并查集
    • 定义
    • 举例说明
    • 查找某个元素属于哪个集合
    • 代码实现
    • 路径压缩
  • Kruskal
    • 算法原理
    • 代码实现
  • Prim
    • 算法原理
    • 代码实现

并查集

定义

🚀在一些应用问题中,需要将n个不同的元素分成一些不相交的集合。开始时,每个元素自成一个单元素集合,然后按一定的规律将归于同一组元素的集合合并。在此过程中,要反复用到查询某一个元素属于哪个集合的运算。适合描述这类问题的抽象数据结构叫做并查集。

🚀由于每个集合就是一颗树形结构,一个并查集内存在多个集合,所以并查集是一个森林。
🚀通常用数组来充当这种数据结构,采用双亲表示法的方式,即子节点存储父节点的指针。

举例说明

🚀例如,有10名同学,它们的编号分别为0-9,它们来自三个班级(1班,2班,3班),其中0,3,4号同学来自一班,1,7,9同学来自2班,2,5,6,8号同学来自3班。

初始结构:每个元素各自独立为一个集合。
在这里插入图片描述
元素的合并:相同班级的同学合并为同一个集合。

在这里插入图片描述

🚀合并规则:

将子节点的数据加到父节点的数据中,将字节点的数据改为父节点的指针。这样父节点中的数据的绝对值就是此集合中元素个数,各个子节点存储的都是指向父节点的指针。

🚀合并的优化:

在合并时,最好将集合内元素数量较少的集合合并到集合内元素数量多的集合。这样在查找某个元素属于哪个集合时可以减少时间消耗。例如,将1班和3班合并,优先是将1班合并到3班,这样第三层的结点数量比较少,相比于将3班合并到1班。
在这里插入图片描述

查找某个元素属于哪个集合

在这里插入图片描述
🚀例如查找n号下元素属于哪个集合,只需要判断n号位置的元素是否为负数,如果为负数说明n号位置就是这个集合的根节点(用根节点的下标来标识集合),如果不是负数就顺着父节点向上访问,直到根节点为止。

代码实现

#pragma once

#include <vector>
#include <iostream>

/*
*					并查集
* 1.核心采用双亲表示法,孩子结点存储的是父节点的下表
* 2.采用类似堆的结构表示---在数组中存储
*/
class UnionFindSet {
private:
	std::vector<int> _ufs;
public:
	UnionFindSet(size_t n) :_ufs(n,-1) {}
	int FindRoot(int x) {
		int parent = x;
		//一直向上找到存储负数的结点
		while (_ufs[parent] >= 0) {
			parent = _ufs[parent];
		}
		return parent;
	}
	void Union(int x1, int x2) {
		int root1 = FindRoot(x1);
		int root2 = FindRoot(x2);
		if (root1 == root2) { return; } //本身就在一个集合当中
		if (abs(_ufs[root1]) < abs(_ufs[root2])) {
			std::swap(root1, root2);
		}
		_ufs[root1] += _ufs[root2];     //将root2结点的数据加到root1结点的数据上
		_ufs[root2] = root1;            //将root2结点的数据修改为root1(根结点的下标)
	}
	size_t SetSize() const {
		size_t cnt = 0;
		for (auto& e : _ufs) {
			if (e < 0) { cnt++; }  //结点数据为0的就表示为根结点,能够代表一个集合
		}
		return cnt;
	}
	bool InSet(int x1, int x2)  {
		int root1 = FindRoot(x1);
		int root2 = FindRoot(x2);
		return root1 == root2;
	}
};

路径压缩

🚀在经过多次集合合并时,可能会出现某个集合的树形结构深度很深,从而导致查询某个元素的集合时时间消耗比较大,所以可以对树形结构的路径进行压缩。

在这里插入图片描述
上图只是个示例,通常来说数据量很大的时候才需要路径压缩。

压缩策略:

在查找某个元素属于哪个集合时进行路径的压缩,在找到某个元素的根节点后不着急返回,而是依次将此路径上的该元素的父节点合并到根结点处,完成路径压缩。这样下次再查找某个元素属于哪个集合时,就可以提高效率。

int FindRoot(int x) {
	int parent = x;
	//一直向上找到存储负数的结点
	while (_ufs[parent] >= 0) {
		parent = _ufs[parent];
	}
	//核心代码
	int cur = x;
	while (_ufs[cur] >= 0) {
		int tmp = cur;
		cur = _ufs[cur];
		_ufs[tmp] = parent;
	}
	return parent;
}

Kruskal

最小生成树
🚀最小生成树就是在图的所有生成树中找出各个路径权值和最小的那个路径。

算法原理

🚀克鲁斯卡尔算法是求图的最小生成树的一个经典算法,其采用全局贪心的思想,每次都去选权值最小的边(可以将所有边放在一个小根堆中每次获取堆顶元素)去构成最小生成树(不能重复选取某一个边),但是这种贪心方式可能会造成环路的产生,所以在每选一个边之前都要判断一下选取这个边后会不会构成环路。而判环的这个步骤使用并查集是非常适合的,可以将已经选取出来的构成最小生成树的顶点放在一个集合S中,在添加某个边时,如果这个边的两个顶点均位于集合S中,说明构成了环路这条边是不能选取的。

在这里插入图片描述
选取边的过程:

在这里插入图片描述
🚀最小生成树的结果并不唯一,例如上图中有两个权值为8的边,上面的选法中是选取了ah这条边,选取bc这条边也是没有问题的。

代码实现

struct Edge {
	size_t _srci;
	size_t _dsti;
	W _weight;

	Edge(size_t srci,size_t dsti,const W& weight) :_srci(srci),_dsti(dsti),_weight(weight) 
	{}
	bool operator > (const Edge& e) const {
		return _weight > e._weight;
	}
};

W Kruskal(self& min_tree) {
	size_t n = _vertex.size();
	min_tree._vertex = _vertex;
	min_tree._index_map = _index_map;
	//初始化矩阵
	min_tree._matrix.resize(n, std::vector<W>(n, W_MAX));
	//开始计算最小生成树
	std::priority_queue<Edge,std::vector<Edge>,std::greater<Edge>> pq;
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			if (i < j && _matrix[i][j] != W_MAX) { 
			//i<j在无向图中避免同一条边添加两次
				pq.push(Edge(i, j, _matrix[i][j]));
			}
		}
	}
	W total = W();//记录权值和
	size_t size = 0;//记录挑选出的边的个数
	UnionFindSet ufs(n);
	while (size < n - 1 && !pq.empty()) {
		Edge eg = pq.top();
		pq.pop();
		if (ufs.InSet(eg._srci, eg._dsti) == false) {
			min_tree._AddEdge(eg._srci, eg._dsti, eg._weight);
			ufs.Union((int)eg._srci, (int)eg._dsti);
			total += eg._weight;
			++size;
		}
	}
	if (size != n - 1) {
		return W();
	}
	return total;
}

Prim

算法原理

🚀Prim算法与Krunskal算法都是采用贪心的策略,但是Prim算法采用的是局部贪心的策略,在Prim算法中会将图的顶点分类到两个集合X,Y中,对于已经选入到最小生成树的边相连的顶点放在X集合中,没有选进最小生成树的顶点放入Y集合。而在贪心选取权值最小的边时,是在一个顶点位于X集合另一个顶点位于Y集合的所有边中去选择的。所以Prim算法需要指定一个起始顶点。当某个顶点被添加到X集合后,要将与其相连的边放入到优先级队列中,放入优先级队列的边也是有要求的就是它另一个顶点必须是在Y集合中的(如果采用优先级队列的方式存储边,仍然会出现环路情况,所以在选边的同时也要注意判环操作)

在这里插入图片描述

选取边的过程
假设起始顶点为a:

在这里插入图片描述

代码实现

W Prim(self& min_tree, const V& src) {
	size_t n = _vertex.size();
	min_tree._vertex = _vertex;
	min_tree._index_map = _index_map;
	//初始化矩阵
	min_tree._matrix.resize(n, std::vector<W>(n, W_MAX));
	//将所有的顶点划分为两个集合 X Y,已经选择的点放入X集合中,没有选择的点在Y集合中
	size_t srci = this->GetVertexIndex(src);
	std::vector<bool> X(n, false);
	std::vector<bool> Y(n, true);
	X[srci] = true;
	Y[srci] = false;
	//将与X集合中所有顶点相连的边存入优先级队列,以供贪心选择
	std::priority_queue<Edge, std::vector<Edge>, std::greater<Edge>> pq;
	for (size_t i = 0; i < n; ++i) {
		if (_matrix[srci][i] != W_MAX) {
			pq.push(Edge(srci, i, _matrix[srci][i]));
		}
	}
	
	size_t size = 0;
	W total_w = W();
	while (size < n - 1 && !pq.empty()) {
		Edge eg = pq.top();
		pq.pop();
		//防止选出的边构成环
		if (true == X[eg._dsti]) {
			continue;
		}
		X[eg._dsti] = true;
		Y[eg._dsti] = false;
		min_tree._AddEdge(eg._srci, eg._dsti, eg._weight);
		++size;
		total_w += eg._weight;
		//std::cout << _vertex[eg._srci] << "->" << _vertex[eg._dsti] << ":" << eg._weight << std::endl;
		//将以dsti点为起点的边加入到队列中
		for (size_t i = 0; i < n; i++) {
			//存在边且终点位于Y集合中
			if (_matrix[eg._dsti][i] != W_MAX && Y[i]) {
				pq.push(Edge(eg._dsti, i, _matrix[eg._dsti][i]));
			}
		}
	}
	if (size != n - 1) {
		return W();
	}
	return total_w;
}

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

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

相关文章

小商品公众号微信店铺搭建的作用是什么

小商品顾名思义就是价格低、需求广且数量多的日用产品&#xff0c;覆盖人群非常广&#xff0c;无论线上还是线下总能找到目标客户&#xff0c;铅笔、削皮刀、晾衣架等各式产品琳琅满目&#xff0c;不少商家也是热衷于小商品的售卖。 从整体来看&#xff0c;小商品商家也可线上…

tomcat整体架构

Tomcat介绍 Tomcat是Apache Software Foundation&#xff08;Apache软件基金会&#xff09;开发的一款开源的Java Servlet 容器。它是一种Web服务器&#xff0c;用于在服务器端运行Java Servlet和JavaServer Pages (JSP)技术。它可 以为Java Web应用程序提供运行环境&#x…

刚入职字节外包一个月,我却离职了...

有一种打工人的羡慕&#xff0c;叫做“大厂”。 真是年少不知大厂香&#xff0c;错把青春插稻秧。 但是&#xff0c;在深圳有一群比大厂员工更庞大的群体&#xff0c;他们顶着大厂的“名”&#xff0c;做着大厂的工作&#xff0c;还可以享受大厂的伙食&#xff0c;却没有大厂…

新手选MT4还是MT5,anzo capital昂首资本建议选择MT4,一个原因

在交易中就订单执行策略而言&#xff0c;MT4和MT5哪个更好&#xff0c;相信很多交易者和&#xff0c;anzo capital昂首资本一样很难做出判断。在MT5中&#xff0c;虽然开发人员对发送订单的流程进行了额外的复杂化&#xff0c;同时MT5在订单执行政策方面的优势在于其能够调整全…

告警繁杂迷人眼,多源分析见月明

随着数字化浪潮的蓬勃兴起&#xff0c;网络安全问题日趋凸显&#xff0c;面对指数级增长的威胁和告警&#xff0c;传统的安全防御往往力不从心。网内业务逻辑不规范、安全设备技术不成熟都会导致安全设备触发告警。如何在海量众多安全告警中识别出真正的网络安全攻击事件成为安…

Vue3项目使用Stimulsoft.Reports.js【项目实战】

Vue3项目使用Stimulsoft.Reports.js【项目实战】 相关阅读&#xff1a;vue-cli使用stimulsoft.reports.js&#xff08;保姆级教程&#xff09;_stimulsoft vue-CSDN博客 前言 在BS的项目中我们时常会用到报表打印、标签打印、单据打印&#xff0c;可是BS的通用打印解决方案又…

【JavaEE初阶】 多线程(初阶)——壹

文章目录 &#x1f332;线程的概念&#x1f6a9;线程是什么&#x1f6a9;为啥要有线程&#x1f6a9;进程和线程的区别&#x1f6a9;Java 的线程 和 操作系统线程 的关系 &#x1f60e;第一个多线程程序&#x1f6a9;使用 jconsole 命令观察线程 &#x1f38d;创建线程&#x1f…

字段位置顺序对值的影响

Unity中验证AB加载场景时报错&#xff1a; Cannot load scene: Invalid scene name (empty string) and invalid build index -1 报错原因是因为把字段放在了Start函数后面(图一)改成(图二)就好了。图一中协程使用的sceneBName字段值为null。 图一&#xff1a; 图二&#xff1a…

【C++】List -- 详解

一、list的介绍及使用 https://cplusplus.com/reference/list/list/?kwlist list 是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。 list 的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&…

Windows10点击开始菜单没反应的四种解决方法

在Windows10电脑中&#xff0c;用户点击开始菜单却出现了没反映的情况&#xff0c;这样用户就无法通过开始菜单来展开操作哦&#xff0c;会给用户的正常操作带来一定程序的影响&#xff0c;下面小编给大家带来四种简单的解决方法&#xff0c;帮助大家轻松恢复Windows10电脑开始…

Selenium 高级定位 CSS

一、CSS选择器概念 CSS拥有自己的语法规则和表达式 CSS通常分为相对定位和绝对定位 CSS常和XPATH一起用于UI自动化测试 二、CSS相对定位使用场景 支持web场景支持app端的webview 三、CSS语法实战 3.1、CSS相对定位的优点 可维护性强语法简洁可以解决各种复杂的定位场景 # …

ARMv7-A 那些事 - 6.常用汇编指令

By: Ailson Jack Date: 2023.10.07 个人博客&#xff1a;http://www.only2fire.com/ 本文在我博客的地址是&#xff1a;http://www.only2fire.com/archives/158.html&#xff0c;排版更好&#xff0c;便于学习&#xff0c;也可以去我博客逛逛&#xff0c;兴许有你想要的内容呢。…

【二叉树练习题】

欢迎来到我的&#xff1a;世界 希望作者的文章对你有所帮助&#xff0c;有不足的地方还请指正&#xff0c;大家一起学习交流 ! 目录 前言初阶题二叉树的节点个数二叉树的叶子节点个数二叉树第k层节点个数二叉树查找值为x的节点 进阶题完全二叉树的节点个数翻转二叉树检验两个树…

修炼k8s+flink+hdfs+dlink(一:安装dlink)

一&#xff1a;mysql初始化。 mysql -uroot -p123456 create database dinky; grant all privileges on dinky.* to dinky% identified by dinky with grant option; flush privileges;二&#xff1a;上传dinky。 上传至目录/opt/app/dlink tar -zxvf dlink-release-0.7.4.t…

医学访问学者面试技巧

医学访问学者面试是一个非常重要的环节&#xff0c;它决定了你是否能够获得这个宝贵的机会去国外的大学或研究机构学习和研究。在这篇文章中&#xff0c;知识人网小编将分享一些关于医学访问学者面试的技巧&#xff0c;帮助你在面试中表现出色。 1. 准备充分 在参加医学访问学…

Multisim:JFET混频器设计(含完整程序)

目录 前言实验内容一、先看作业题目要求二、作业正文IntroductionPre-lab work3.13.2 Experiment Work4.1(2)circuit setup4.1(3)add 12V DC4.1(4)set input x1 and x24.1(5)4.1(6)4.1(7)4.2(1)(2)4.2(3)4.2(4)4.3(1)(2)4.3(3) Conclusion 三、资源包内容 前言 花了好大心血完成…

【线性代数及其应用 —— 第一章 线性代数中的线性方程组】-1.线性方程组

所有笔记请看&#xff1a; 博客学习目录_Howe_xixi的博客-CSDN博客https://blog.csdn.net/weixin_44362628/article/details/126020573?spm1001.2014.3001.5502思维导图如下&#xff1a; 内容笔记如下&#xff1a;

中国高清行政、地形、旅游、人文地图全集(地理干货,覆盖34个省市自治区)

中国高清行政、地形、旅游、人文地图全集&#xff08;地理干货&#xff0c;覆盖34个省市自治区&#xff09;&#xff1a; 中国的高原、平原、盆地和丘陵 四大高原&#xff1a;青藏高原位于中国西南部&#xff0c;平均海拔在4000米以上&#xff0c;是中国最大、世界最高的大高原…

安卓手机如何下载谷歌应用商店(play.google.com)里的app,无需下载google play

不需要梯子&#xff01; 不需要下载google play&#xff01; 不需要考虑机型&#xff01; 1、首先使用电脑打开网站 https://apps.evozi.com/ 点击 apk downloader 进入一个新的界面 2、电脑打开谷歌商店 https://play.google.com/store/apps 搜索任意软件&#xff0c;比如 …

300元开放式耳机推荐哪款好用一点、最便宜的开放式耳机

对于音乐爱好者来说&#xff0c;一款出色的耳机是必不可少的&#xff0c;开放式耳机以其独特的音场表现和舒适的佩戴感受&#xff0c;成为许多人钟爱的选择。如果你正在寻找一个300元价位好用的开放式耳机&#xff0c;今天就给大家推荐几款&#xff0c;希望能帮助到你~ 1、西圣…