最小生成树—Kruskal算法和Prim算法

news2024/9/21 18:38:13

1.最小生成树

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

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

最小生成树:构成生成树的这些边加起来权值最小的

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

若连通图由n个顶点组成,则其生成树必含n个顶点和n-1条边。因此构造最小生成树的准则有三
条:
        1. 只能使用图中权值最小的边来构造最小生成树
        2. 只能使用恰好n-1条边来连接图中的n个顶点
        3. 选用的n-1条边不能构成回路

构造最小生成树的方法:Kruskal算法和Prim算法。这两个算法都采用了逐步求解的贪心策略。
贪心算法:是指在问题求解时,总是做出当前看起来最好的选择。也就是说贪心算法做出的不是
整体
最优的的选择,而是某种意义上的局部最优解。贪心算法不是对所有的问题都能得到整体最优解。

2.Kruskal算法

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

实现思路:借助优先级队列将每一条权值按照从小到大的顺序存储起来,依次取出优先级队列中的值,判断当前边做为最小生成树的边是否构成回路,如果构成构成回路则采用并查集进行排除。

3.Prim算法

实现思路:给一个源点,借助两个vector数组X和Y,从X中加入源点,然后在Y中选择权值最小的边添加到优先级队列中,然后依次去除优先级队列中的值,如果最小边的目标点也在X集合中,就说明构成环了,否则该边就是最小边,进行添加边,然后更新X数组和Y数组继续选边

4.代码实现

#include<iostream>
#include<vector>
#include<map>
#include<string>
#include<queue>
#include<functional>
using namespace std;

namespace matrix
{
	class UnionFindSet
	{
	public:
		UnionFindSet(size_t size)
			: _ufs(size, -1) {}
		int FindRoot(size_t index)
		{
			while (_ufs[index] >= 0)
			{
				index = _ufs[index];
			}
			return index;
		}
		bool Union(int x1, int x2)
		{
			int root1 = FindRoot(x1);
			int root2 = FindRoot(x2);
			if (root1 == root2)
				return false;
			_ufs[root1] += _ufs[root2];
			_ufs[root2] = root1;
			return true;
		}
		bool InSet(int x1, int x2)
		{
			return FindRoot(x1) == FindRoot(x2);
		}
		int Count()
		{
			int count = 0;
			for (auto& e : _ufs)
			{
				if (e < 0)
					count++;
			}
			return count;
		}
	private:
		vector<int> _ufs;
	};
	template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>
	class Graph
	{
		typedef Graph<V,W, MAX_W, Direction> self;
	public:
		Graph() = default;
		Graph(const V* vertexs, size_t n)
		{
			_vertexs.reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				_vertexs.push_back(vertexs[i]);
				_indexMap[vertexs[i]] = i;
			}
			_matrix.resize(n);
			for (auto& e : _matrix)
			{
				e.resize(n, MAX_W);
			}
		}
		void _addEdge(size_t srci, size_t dsti, const W& w)
		{
			_matrix[srci][dsti] = w;
			if (Direction == false)
				_matrix[dsti][srci] = w;
		}
		size_t GetVertexIndex(const V& v)
		{
			auto ret = _indexMap.find(v);
			if (ret != _indexMap.end())
				return ret->second;
			else
				return -1;
		}
		//添加边:
		void AddEdge(const V& src, const V& dst, const W& w)
		{
			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 << _vertexs[i] << "-" << i << " ";
			}
			cout << endl;
			for (size_t i = 0; i < _vertexs.size(); i++)
			{
				if (i == 0)
					cout << "  ";
				cout << i << " ";
			}
			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] != INT_MAX)
						cout << _matrix[i][j] << " ";
					else
						cout << "*" << " ";
				}
				cout << endl;
			}
			cout << endl;
			//打印所有的边:
			for (size_t i = 0; i < _matrix.size(); i++)
			{
				for (size_t j = 0; j < _matrix[i].size(); j++)
				{
					if (_matrix[i][j] != INT_MAX)
						cout << _vertexs[i] << "-" << _vertexs[j] << ":" << _matrix[i][j] << endl;
				}
			}
		}
		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 (int i = 0; i < n; i++)
			{
				minTree._matrix[i].resize(n, MAX_W);
			}
			priority_queue<Edge, vector<Edge>, greater<Edge>> minq;
			for (int i = 0; i < n; i++)
			{
				for (int j = 0; j < n; j++)
				{
					//i < j 避免相同的边加入两次
					if (i < j && _matrix[i][j] != MAX_W)
					{
						minq.push(Edge(i, j, _matrix[i][j]));
					}
				}
			}
			//选出n-1条边:
			int size = 0;
			W totalw = W();
			UnionFindSet ufs(n);
			while (!minq.empty())
			{
				Edge min = minq.top();
				minq.pop();
				if (!ufs.InSet(min._srci, min._dsti))
				{
					minTree._addEdge(min._srci, min._dsti,min._w);
					ufs.Union(min._srci, min._dsti);
					++size;
					totalw += min._w;
				}
			}
			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 (int 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;
			priority_queue<Edge, vector<Edge>, greater<Edge>> minq;
			for (size_t i = 0; i < n; i++)
			{
				if (_matrix[srci][i] != MAX_W)
					minq.push(Edge(srci, i, _matrix[srci][i]));
			}
			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;
			return W();
		}
	private:
		vector<V> _vertexs; //存储顶点的集合
		vector<vector<W>> _matrix; //存储边集合的矩阵
		map<V, int> _indexMap;//顶点映射下标
	};
	void TestGraphMinTree()
	{
		const char* str = "abcdefghi";
		Graph<char, int> g(str, strlen(str));
		g.AddEdge('a', 'b', 4);
		g.AddEdge('a', 'h', 8);
		g.AddEdge('b', 'c', 8);
		g.AddEdge('b', 'h', 11);
		g.AddEdge('c', 'i', 2);
		g.AddEdge('c', 'f', 4);
		g.AddEdge('c', 'd', 7);
		g.AddEdge('d', 'f', 14);
		g.AddEdge('d', 'e', 9);
		g.AddEdge('e', 'f', 10);
		g.AddEdge('f', 'g', 2);
		g.AddEdge('g', 'h', 1);
		g.AddEdge('g', 'i', 6);
		g.AddEdge('h', 'i', 7);
		Graph<char, int> kminTree;
		cout << "Kruskal:" << g.Kruskal(kminTree) << endl;
		kminTree.Print();
		Graph<char, int> pminTree;
		cout << "Prim:" << g.Prim(pminTree,'a') << endl;
		pminTree.Print();
	}
}
int main()
{
	matrix::TestGraphMinTree();
	return 0;
}

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

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

相关文章

Java基础-面向对象总结(3)

本篇文章主要讲解Java面向对象的知识点 面向对象的三大特性类的扩展(抽象类,接口,内部类,枚举) 目录 面向对象和面向过程的区别? 面向对象的五大基本原则 面向对象三大特性 继承 怎么理解继承 ? 继承和聚合的区别&#xff1f; 封装 多态 什么是多态 什么是运行时多…

面试阿里、字节全都一面挂,被面试官说我的水平还不如应届生

测试员可以先在大厂镀金&#xff0c;以后去中小厂毫无压力&#xff0c;基本不会被卡&#xff0c;事实果真如此吗&#xff1f;但是在我身上却是给了我很大一巴掌... 所谓大厂镀金只是不卡简历而已&#xff0c;如果面试答得稀烂&#xff0c;人家根本不会要你。况且要不是大厂出来…

MySQL数据库基础4-内置函数

文章目录 日期函数字符串函数数学函数其他函数 日期函数 函数名称描述current date()当前日期current time()当前时间current timestamp()当前时间戳date(datetime)返回datetime参数的日期部分date add(date, interval d_value type)在date中添加日期或时间&#xff0c;interv…

GitHub Actions Error “Waiting for a runner to pick up this job”

GitHub Actions Error “Waiting for a runner to pick up this job” 什么是GitHub Actions GitHub Actions 是一个 CI/CD&#xff08;持续集成和持续部署&#xff09;平台&#xff0c;可以让您自动化工作流程并与 GitHub 存储库中的代码集成。使用 GitHub Actions&#xff…

智能排班系统 【数据库设计】

文章目录 数据库设计规范ER图物理模型数据表登录日志表操作日志表菜单表角色表企业表门店表省市区表门店节日表消息表职位表排班规则表排班任务表排班结果存储scheduling_date排班日表scheduling_shift排班班次表shift_user班次员工中间表 定时通知表用户表中间表role_menu角色…

适合小白的网络安全书籍推荐

学习的方法有很多种&#xff0c;看书就是一种不错的方法&#xff0c;但为什么总有人说&#xff1a;“看书是学不会技术的”。 其实就是书籍没选对&#xff0c;看的书不好&#xff0c;你学不下去是很正常的。 一本好书其实不亚于一套好的视频教程&#xff0c;尤其是经典的好书…

MATLAB系列(2)——plot画图函数

一、plot plot画的是折线图。plot可以画出多种线类型的图&#xff0c;比如实线、虚线、星线、圆圈线等,一个图里可以画多条折线&#xff0c;方便对比。 1.1 设置坐标轴标签 和 图名 使用xlabel 和ylabel&#xff0c;title设置图名&#xff0c;fontsize设置名字字体大小 1.2 …

Netty核心技术三--NIO编程

1. JAVA NIO基本介绍 Java NIO 全称 java non-blocking IO&#xff0c;是指 JDK 提供的新API。从 JDK1.4 开始&#xff0c;Java 提供了一系列改进的输入/输出的新特性&#xff0c;被统称为 NIO(即 New IO)&#xff0c;是同步非阻塞的 NIO 相关类都被放在 java.nio 包及子包下&…

【软件测试基础】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 1、什么是软件测试(CASE) 1.1 软件测试就是验…

亲测解决:项目无法编译/打包,提示找不到符号的问题

我在对服务进行打包的过程中遇到了“Error…找不到符号”的问题&#xff0c;但是我的项目是能够正常启动的&#xff0c;为什么会出现这个问题呢&#xff1f; 有的博主说是因为我没有正常打包&#xff0c;然后我又学习了一遍如何对项目进行打包…但是然并卵&#xff08;然而并没…

【Java数据结构】——第十节(下).选择排序与堆排序

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;Java初阶数据结构 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01; 文章目…

一文3000字从0到1手把手教你基于PostMan的接口自动化测试

临近下半年&#xff0c;公司任务也不是很多&#xff0c;趁这个机会老大让我研究了一下PostMan的脚本自动化测试。作为一个前端开发&#xff0c;说实话&#xff0c;对于PostMan的操作&#xff0c;仅仅限于新建请求->填写url地址和参数->send发送&#xff0c;然后看看返回值…

智能排班系统 【开源说明】

文章目录 引言说明讲讲开源开源要做什么了解开源协议项目的信息脱敏写好项目说明文档修改.gitignore 项目不完善点说明管理系统前端页面自适应做得不好部分页面体验不好 管理系统后端接口缺乏数据校验数据管理接口查询不够完善接口可以更加完善 开源仓库地址其他文章引用说明前…

【我的创作纪念日】—— 纪念四年的坚持

这是一篇和技术无关的博客&#xff0c;但对我而言&#xff0c;它承载了不菲的价值 普通且宁静的一天&#xff0c;被一条消息戳中&#xff0c;于是&#xff0c;写一篇分享帖&#xff0c;纪念我这 1460 天的坚持初衷&#xff1a; 前言&#xff1a;对过去的回顾 4 年前的我&#…

南京邮电大学算法与设计实验二:贪心算法(最全最新,与题目要求一致)

三、实验原理及内容 实验原理&#xff1a; 1、用贪心法实现求两序列的一般背包问题。要求掌握贪心法思想在实际中的应用&#xff0c;分析一般背包的问题特征&#xff0c;选择算法策略并设计具体算法&#xff0c;编程实现贪心选择策略的比较&#xff0c;并输出最优解和最优解值。…

图的遍历,最小生成树,最短路径算法的手算。

1.图的遍历 按照某种规则沿着图中的边对图中的所有顶点访问一次且仅访问一次。 注&#xff1a;图是一种特殊的树。 1.广度优先遍历BFS 不难看出&#xff0c;图的广度优先就是参照的树的层次遍历算法。 2.深度优先遍历 从某个顶点开始V&#xff0c;访问这个顶点V相邻的任意…

【音视频开发】视频编码格式:YUV

文章目录 分类标准分类 简介 参考&#xff1a;YCbCr与YUV 分类标准 首先&#xff0c;我们可以将 YUV 格式按照数据大小分为三个格式&#xff0c;YUV 420&#xff0c;YUV 422&#xff0c;YUV 444。由于人眼对 Y 的敏感度远超于对 U 和 V 的敏感&#xff0c;所以有时候可以多个 …

位运算符及其相关操作详解

位运算符详解 前言&#xff1a;由于位运算符是直接对二进制数操作&#xff0c;因此对二进制、八进制、十六进制不甚了解的小伙伴建议先看这篇二进制、八进制、十六进制与十进制的相互关系&#xff0c;这样阅读本篇时将事半功倍 总览 位运算是对计算机存储的二进制序列的相应位进…

【笔试强训选择题】Day17.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01; 前言 目…

南京邮电大学算法与设计实验三:动态规划法(最全最新,与题目要求一致)

实验原理&#xff1a; 1、用动态规划法和备忘录方法实现求两序列的最长公共子序列问题。要求掌握动态规划法思想在实际中的应用&#xff0c;分析最长公共子序列的问题特征&#xff0c;选择算法策略并设计具体算法&#xff0c;编程实现两输入序列的比较&#xff0c;并输出它们的…