【数据结构】无向图的最小生成树(Prime,Kruskal算法)

news2025/2/4 19:07:45

文章目录

  • 前言
  • 一、最小生成树
  • 二、Kruskal算法
    • 1.方法:
    • 2.判断是否成环
    • 3.代码实现
  • 三、 Prim算法
    • 1.方法:
    • 2.代码
  • 四、源码


前言

连通图:在无向图中,若从顶点v1到顶点v2有路径,则称顶点v1与顶点v2是连通的。如果图中任意一对顶点都是连通的,则称此图为连通图

强连通图:在有向图中,若在每一对顶点vi和vj之间都存在一条从vi到vj的路径,也存在一条从vj到vi的路径,则称此图是强连通图

生成树:一个连通图的最小连通子图称作该图的生成树。有n个顶点的连通图的生成树有n个顶点和n-1条边

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

一、最小生成树

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

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

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

二、Kruskal算法

里面涉及一些图的代码,不了解的可以参考之前我写的【数据结构】图的创建(邻接矩阵,邻接表)以及深度广度遍历(BFS,DFS)

1.方法:

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

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

2.判断是否成环

我们要保证不能成环需要保证选入某条边之前,这条边两个顶点本身就不连通,换种说法就是两个顶点没有公共的顶点或者不在同一个集合当中,这个时候我们就可以考虑之前学的并查集的知识来解决了,并查集就是解决两个元素的联合与判断是否在一个结合中的数据结构。

不知道小伙伴可以去看看之前我写的【数据结构】并查集的简单实现,合并,查找(C++)

3.代码实现

typedef Graph<V, W, MAX_W, false> Self;

		//建立边的类,保存边的两个顶点下标和权值
		struct Edge {
			size_t _srci;
			size_t _dsti;
			W _w;

			Edge(size_t srci,size_t dsti,W w)
				:_srci(srci),
				_dsti(dsti),
				_w(w)
			{}

			bool operator>(const Edge& e)const {
				return _w > e._w;//小根堆判断
			}

		};

		W Kruskal(Self& minTree)
		{
			//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]));
					}
				}
			}

			 
			int size = 0;
			W totalW = W();
			UnionFindSet ufs(n); 

			// 选出n-1条边
			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();
			}
		}

三、 Prim算法

1.方法:

Prim算法采用的局部贪心的策略,从当前选入的局部的点中选取最小的边
在这里插入图片描述
在这里插入图片描述

Prim算法的优点在于他不会构成环,他是以顶点为单位进行筛选,X表示已经选入的点,Y中存的未选入的点,X从Y中选顶点加入到X中,然后Y删除这个顶点,(若X中出现两个相同顶点则构成环),但这种情况基本不大可能出现,因为我们每次都是从Y中选,都是X中没有的顶点

2.代码

typedef Graph<V, W, MAX_W, false> Self;

		//建立边的类,保存边的两个顶点下标和权值
		struct Edge {
			size_t _srci;
			size_t _dsti;
			W _w;

			Edge(size_t srci,size_t dsti,W w)
				:_srci(srci),
				_dsti(dsti),
				_w(w)
			{}

			bool operator>(const Edge& e)const {
				return _w > e._w;//小根堆判断
			}

		};
W Prim(Self& minTree, const W& 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);
			}

			 

			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
				{
					//从Y中选出顶点
					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();
			}

四、源码

namespace matrix {
	//V为顶点类型,W为边权值类型,MAX_W为权值最大值也就是无效值
	//Direction用来判断是不是有向图,false为无向图
	template<class V,class W,W  MAX_W=INT_MAX,bool Direction=false>
	class Graph {
	public:
		Graph() = default;
		Graph(const V* a, size_t n) {
			_vertexs.reserve(n);
			for (size_t i = 0; i < n; i++) {
				_vertexs.push_back(a[i]);
				_indexMap[a[i]] = i;
				//将顶点存入_vertexs,下标映射存进map
			}

			_matrix.resize(n);
			for (size_t i = 0; i < _matrix.size(); i++) {
				_matrix[i].resize(n, MAX_W);
				//邻接矩阵默认初始值为无效值
			}
		}

		size_t GetVertexIndex(const V& v) {
			//获得对应顶点在数组中的下标
			auto it = _indexMap.find(v);
			if (it != _indexMap.end()) {
				return it->second;
				//有这个顶点返回其下标
			}
			else {
				throw("顶点不存在");
				return -1;
			}
		}

		void _AddEdge(size_t srci, size_t dsti, const W& w) {
			//存入权值
			_matrix[srci][dsti] = w;
			if (Direction == false) {
				_matrix[dsti][srci] = w;
				//无向图要两个方向都存
			}
		}

		void AddEdge(const V& src, const V& dst, const W& w) {
			//添加边与顶点的关系。从src到dst方向的关系
			size_t srci = GetVertexIndex(src);
			size_t dsti = GetVertexIndex(dst);
			//先获取其对应的下标
			_AddEdge(srci, dsti, w);
		}

		void Print() {
			for (size_t i = 0; i < _vertexs.size(); i++) {
				cout << "[" << i << "]" << "->" << _vertexs[i] << endl;
			}//打印顶点集
			cout << endl;


			//打印邻接矩阵
			for (size_t i = 0; i < _matrix.size(); i++) {
				cout << i << " ";
				for (size_t j = 0; j < _matrix[i].size(); j++) {
					if (_matrix[i][j] == MAX_W) {
						printf("%4c", '*');
					}
					else {
						printf("%4d", _matrix[i][j]);
					}
				}
				cout << endl;
			 }
		}

	 
	 


		void BFS(const V& src) {
			size_t srci = GetVertexIndex(src);
			queue<int>q;
			q.push(srci);
			vector<bool>visited(_vertexs.size(), false);
			visited[srci] = true;//标记这个顶点被访问过了
			int levelSize = 1;
			while (!q.empty()) {
				//levelSize为当前层的大小
				for (size_t i = 0; i < levelSize; i++) {
					int front = q.front();
					q.pop();
					cout << front << ":" << _vertexs[front]<<" ";

					for (size_t i = 0; i < _vertexs.size(); i++) {
						if (_matrix[front][i] != MAX_W && visited[i] == false) {
							q.push(i);
							visited[i] = true;//标记这个顶点被访问过了
						}
					}
				}
				levelSize = q.size();//更新当前层的数量
				cout << endl;
			}
			cout << endl;
		}


		void _DFS(size_t srci, vector<bool>& visited) {
			cout << srci << ":" << _vertexs[srci] << endl;
			visited[srci] = true;//标记这个顶点被访问过了
			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);
		}


 

	 
		 
		
		
		 

		typedef Graph<V, W, MAX_W, false> Self;

		//建立边的类,保存边的两个顶点下标和权值
		struct Edge {
			size_t _srci;
			size_t _dsti;
			W _w;

			Edge(size_t srci,size_t dsti,W w)
				:_srci(srci),
				_dsti(dsti),
				_w(w)
			{}

			bool operator>(const Edge& e)const {
				return _w > e._w;//小根堆判断
			}

		};

		W Kruskal(Self& minTree)
		{
			//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]));
					}
				}
			}

			 
			int size = 0;
			W totalW = W();
			UnionFindSet ufs(n); 

			// 选出n-1条边
			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();
			}
		}

		W Prim(Self& minTree, const W& 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);
			}

			 

			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
				{
					//从Y中选出顶点
					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();
			}
		}

	private:
		vector<V>_vertexs;//顶点集合
		map<V, int>_indexMap;//存顶点与数组下标的映射关系
		vector<vector<W>>_matrix;//邻接矩阵
	};



 }

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

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

相关文章

医院信息化-6 大模型与医疗

之前写了一系列跟医疗信息化相关的内容&#xff0c;其中有提到人工智能&#xff0c;但是写的都是原先的一些AI算法基础上的医疗应用。现在大模型出现的涌现推理能力确实让人惊讶&#xff0c;并且出现可商用化的可能性&#xff0c;因此最近一年关于大模型在医疗的应用也开始出现…

ComfyUI如何中文汉化

comfyui中文地址如下&#xff1a; https://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Translationhttps://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Translation如何安装&#xff1f; 1. git安装 进入项目目录下的custom_nodes目录下&#xff0c;然后进入控制台&#xff0c;运…

Java——基本数据类型

Java基本数据类型 一、 整型1. byte2. short3. int4. long 二、浮点型1. float2. double 三、 字符型(char)四、 布尔型&#xff08;boolean&#xff09; 总结 算下刚转Java到现在也有三个多月了&#xff0c;所以打算对Java的知识进行汇总一下&#xff0c;本篇文章介绍一下Java…

Linux之用户/组 管理

关机&重启命令 shutdown -h now立刻进行关机shutdown -h 11分钟后关机&#xff08;shutdown默认等于shutdown -h 1) -h即halt shutdown -r now现在重新启动计算机 -r即reboot halt关机reboot重新启动计算机sync把内存数据同步到磁盘 再进行shutdown/reboot/halt命令在执行…

【支持向量机】SVM线性可分支持向量机学习算法——硬间隔最大化支持向量机及例题详解

支特向量机(support vector machines, SVM)是一种二类分类模型。它的基本模型是定义在特征空间上的间隔最大的线性分类器。包含线性可分支持向量机、 线性支持向量机、非线性支持向量机。 当训练数据线性可分时&#xff0c;通过硬间隔最大化学习线性分类器&#xff0c; 即为线性…

提升泵类设备性能的解决方案:基于AI的预测性维护

随着工业的智能化和数字化发展&#xff0c;设备维护的方式得到不断优化。人工智能&#xff08;AI&#xff09;、机器学习和云计算等先进技术的引入&#xff0c;使得设备健康管理系统的数据采集、实时分析、故障预警与智能诊断等能力得到提升。借助这些设备预测性维护手段&#…

LISN到底是啥?干啥用的?

LISN是在EMC测试的时候&#xff0c;会被使用的设备&#xff0c;如下图所示&#xff1a; 双路V型电源阻抗稳定网络。它完全符合CISPR16-1-2、MIL-STD 461F、VDE 0876、FCC Part 15标准的要求&#xff0c;其等效电路为50Ω||&#xff08;5Ω50μH&#xff09;&#xff0c;频率范围…

vue3使用mixins

<template><div>{{ num }}___{{ fav }}</div><button click"favBtn">改变值</button> </template><script setup lang"ts"> import mixin from "../mixins/mixin"; let { num, fav, favBtn } mixin(…

【微服务核心】Spring Boot

Spring Boot 文章目录 Spring Boot1. 简介2. 开发步骤3. 配置文件4. 整合 Spring MVC 功能5. 整合 Druid 和 Mybatis6. 使用声明式事务7. AOP整合配置8. SpringBoot项目打包和运行 1. 简介 SpringBoot&#xff0c;开箱即用&#xff0c;设置合理的默认值&#xff0c;同时也可以…

【MySQL】数据库之日志管理、备份与恢复

目录 一、MySQL的日志管理 二、MySQL的完全备份与恢复 物理冷备份&#xff08;完全备份&#xff09;与恢复 数据库上云迁移的方案&#xff1f; 逻辑热备份&#xff08;完全备份&#xff09;与恢复 三、MySQL的增量备份与恢复 1、手动增量备份 2、脚本增量备份 3、增量备…

归并排序之C++实现

描述 归并排序是一种经典的排序算法&#xff0c;采用分治的思想。 归并排序是一种基于分治思想的经典排序算法。它将待排序的数组不断地分成两个子数组&#xff0c;直到每个子数组只有一个元素。然后&#xff0c;对每个子数组进行归并排序&#xff0c;即不断地将两个有序的子数…

ros2中ros_gz_bridge/gazebo安装的注意事项

这个搞错了&#xff1a;这个是安装ros_gz_bridge的&#xff0c;不是安装gazebo的 AT:如果是安装的Harmonic&#xff0c;在安装ros_gz_bridge的时候要从源码编译 ros2完整版里面好像已经包含了gazebo的一个版本 包名应该就是叫ros-humble-ros-ign-gazebo 所以gazebo是作为一个普…

使用tesla gpu 加速大模型,ffmpeg,unity 和 UE等二三维应用

我们知道tesla gpu 没有显示器接口&#xff0c;那么在windows中怎么使用加速unity ue这种三维编辑器呢&#xff0c;答案就是改变注册表来加速相应的三维渲染程序. 1 tesla gpu p40 p100 加速 在windows中使用regedit 来改变 核显配置&#xff0c; 让p100 p40 等等显卡通过核显…

docker-compose部署kafka

docker-compose.yml配置 version: "3" services:kafka:image: bitnami/kafka:latestports:- 7050:7050environment:- KAFKA_ENABLE_KRAFTyes- KAFKA_CFG_PROCESS_ROLESbroker,controller- KAFKA_CFG_CONTROLLER_LISTENER_NAMESCONTROLLER- KAFKA_CFG_LISTENERSPLAIN…

国企和互联网怎么选?

2023年马上就要结束了&#xff0c;天气还是很冷&#xff0c;大家今年的总结做了吗&#xff1f; 正好这两天看到另外一个我关注的博主更新了一个自己的年终总结。其中有一些话令人印象深刻。 未来对我来说&#xff0c;毫无吸引力。原因很简单&#xff0c;当下已经足够令人清醒、…

WordPress主题大前端DUX v8.3源码下载

DUX主题8.3版本更新内容&#xff1a; 新增&#xff1a;Cloudflare Turnstile 免费验证功能 新增&#xff1a;子菜单页面模版&#xff0c;支持多级页面 新增&#xff1a;手机端文章内表格自动出现横向滚动条&#xff0c;可集体或单独设置滚动宽度 新增&#xff1a;标签云页面模版…

Linux:jumpserver过滤命令(5)

我们分配完Linux资产后&#xff0c;如果想让用户无法执行某些命令可以直接写入过滤&#xff0c;当执行带有改字段就会自动拒绝 注意&#xff1a;是字段 当我们禁用了passwd&#xff0c;全部带有passwd的东西都会被禁掉 passwd修改密码的命令和/etc/passwd都会被禁了&#xf…

tensorrt环境安装-可用于深度学习模型加速推理

安装python环境 在anaconda的命令行中输入conda create --name py38 python3.8 安装python环境 然后安装深度学习框架pytorch环境 Previous PyTorch Versions | PyTorch 在这里面选择合适的环境 conda install pytorch1.8.0 torchvision0.9.0 torchaudio0.8.0 cudatoolki…

智能优化算法应用:基于驾驶训练算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于驾驶训练算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于驾驶训练算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.驾驶训练算法4.实验参数设定5.算法结果6.…

【yolov5问题解决】Dataset autodownload failure

自己下载该数据集 https://github.com/ultralytics/yolov5/releases/download/v1.0/coco128.zip 然后解压放到平行目录下