最小生成树Kruskal、Prim算法C++

news2024/11/19 14:43:40

什么是最小生成树

连通图:

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

生成树:

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

最小生成树:

最小生活树是生成树的一个特殊情况,它的边权之和最小。其特点如下:

  1. 只能使用图中权值最小的边来构造最小生成树
  2. 只能使用恰好n-1条边来连接图中的n个顶点
  3. 选用的n-1条边不能构成回路(构成回路会导致有顶点为连通或权值过大)

最小生成树的实际应用

例如城市道路铺设中,如果我们直接使用连通结构,这样两点间的交通必然是最便捷的。可是修路的成本的巨大的,但是又要连通所有城市,这便会想到使用最小生成树。

考虑到经济发达城市人口多、车辆多,我们还需要为其多修建一些道路,则实际中的道路修建与最小生成树的结果不同,但是最小生成树在这些实际场景发挥了很大作用。

Kruskal算法

任给一个有n个顶点的连通网络N = {V,E}。

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

算法思路

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

将权值小的边放入到优先级队列中。依次类推

接下来有一个问题,因为生成树不能构成回路,所以在添加边的时候要处理成环问题。

这个问题的解决就要使用并查集;如果该边已经出现过了,则不选择该边,如果该边不在集合中,则可以选择该边

以下是选边的全部过程:

代码实现

首先我们要创建一个边的数据结构Edge,用于将边存放到优先级队列中。

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)
	{
	}
	//还要提供一个比较函数,因为优先级队列中使用到了greater,greater会调用>函数
	bool operator > (const Edge& ed) const
	{
		return _w > ed._w;
	}
};

实现思路:

1. 将所有的边统计到优先级队列中,(注意临界矩阵只遍历一半)

2. 从优先级队列中选出n-1条边,n为顶点数量

3. 依次取出队列中的元素,判断该元素是否出现过(使用并查集)

4. 如果没有出现过,则添加该边到最小生成树中,并将该边添加到并查集中,再统计权值。

5. 当队列元素取空时,最小生成树中边的数量小于n-1,则表示该图没有连通,则没有最小生成树,直接返回权值的默认值即可。

6. 如果size等于n-1,则表示最小生成树创建成功,返回权值总和即可

//最小生成树
//如果有最小生成树,则返回该树的权值,如果没有最小生成树,则返回默认值
W Kruskal(Self& minTree)
{
	//初始化minTree
	size_t n = _vertexs.size();
	minTree._vertexs = _vertexs;
	minTree._indexMap = _indexMap;
	minTree._matrix = vector<vector<W>>(n, vector<W>(n, MAX_W));
	//1. 将边统计起来,使用优先级队列或排序的方式都可以   
	priority_queue<Edge, vector<Edge>, greater<Edge>> minque;

	for (size_t i = 0; i < n; i++)
	{
		//矩阵中走一半即可  要不然会重复入队列
		for (size_t j = 0; j < i; j++)
		{
			if (_matrix[i][j] != MAX_W)
			{
				minque.push(Edge(i, j, _matrix[i][j]));
			}
		}
	}
	//2.选出n-1条边
	size_t size = 0;
	UnionFindSet ufs(n);   //n个顶点
	W total_W{};            //总的权值
	while (!minque.empty() && size < n)
	{
		Edge min = minque.top();
		minque.pop();

		//3.选出一条边之后看该边在不在当前集合,
		//在就不选择该边,不在就选择该边,并标记
		if (!ufs.IsInset(min._srci, min._dsti))
		{
			cout << _vertexs[min._srci] << "-" << _vertexs[min._dsti] <<
				":" << _matrix[min._srci][min._dsti] << endl;
			minTree._AddEdge(min._srci, min._dsti, min._w);
			ufs.Union(min._srci, min._dsti);
			size++;
			total_W += min._w;
		}
	}
	//如果该图不是连通图,则没有最小生成树
	if (size < n - 1)
	{
		return W();
	}
	return total_W;
}

接下来是一些要注意的点:

1. 要将最小生成树进行初始化,否则添加边时会出现越界问题。

2. 最小生成树其实就是该图的子图,typedef Graph<V, W, MAX_W, Direction> Self; 

3. 注意将顶点放入到并查集中,防止边的重复。

测试用例

void TestGraphMinTree()
{
	const char* str = "abcdefghi";
	matrix::Graph<char, int> g(str, strlen(str));
	g.AddEdge('a', 'b', 4);
	g.AddEdge('a', 'h', 8);
	//g.AddEdge('a', 'h', 9);
	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);
	matrix::Graph<char, int> kminTree;
	cout << "Kruskal:" << g.Kruskal(kminTree) << endl;
	kminTree.Print();
}

Prim算法

算法思路

Prim算法所具有的一个性质是集合A中的边总是构成一棵树。这棵树从一个任意的根节点r开始,一直扩大到图中的所有顶点为止。在每一步连接集合A和A之外的顶点的所有边中,选择一条权值最小的边加入到A中。当算法结束时,A中的边形成一棵最小生成树。

因为添加边只会在当前集合中没有的顶点中进行,所以Prim算法天然避免环的生成,不需要使用并查集来避免环的产生。

代码实现

实现思路:

1. 使用两个数组表示X、Y集合,用于表示当前顶点是否被访问。

2. 从X集合中的顶点选出所有的边,可以使用优先级队列来存放边。

3. 依次从优先级队列中选出权值最小的边

4. 判断该边是否成环---即dest顶点必须在Y集合中

5. 在Y集合中则将该边添加到最小生成树中,然后进行顶点的标记

6. 再将dest顶点连通的边再放入队列中,同时也要判断dest连接的顶点归属Y集合。

7. 如果选出n-1条边,返回生成树的总权值,否则返回默认值


W  Prim(Self& minTree, const W& src)   //src表示从哪个起点开始
{
	size_t srci = GetVertexIndex(src);
	size_t n = _vertexs.size();
	//初始化minTree
	minTree._vertexs = _vertexs;
	minTree._indexMap = _indexMap;
	minTree._matrix = vector<vector<W>>(n, vector<W>(n, MAX_W));

	//两个数组表示X、Y集合,用于表示当前顶点是否被访问
	vector<bool> X(n, false);
	vector<bool> Y(n, true);
	//初始化X,Y集合
	X[srci] = true;
	Y[srci] = false;
	//从X-Y集合连接中的边选出权值最小的边
	priority_queue<Edge, vector<Edge>, greater<Edge>> minque;
	//先把srci连接的边添加到队列中
	for (size_t i = 0; i < n; i++)
	{
		if (_matrix[srci][i] != MAX_W)
		{
			minque.push(Edge(srci, i, _matrix[srci][i]));
		}
	}
	size_t size = 0;
	W total_W = W();
	while (!minque.empty())
	{
		Edge min = minque.top();
		minque.pop();
		if (Y[min._dsti])  //该顶点必须还在Y集合中   注意!!
		{
			cout << _vertexs[min._srci] << "-" << _vertexs[min._dsti] <<
				":" << _matrix[min._srci][min._dsti] << endl;
			minTree._AddEdge(min._srci, min._dsti, min._w);
			total_W += min._w;
			size++;

			if (size == n - 1)
				break;

			X[min._dsti] = true;
			Y[min._dsti] = false;
			//将dsti的边进行遍历
			for (size_t i = 0; i < n; i++)
			{
				if (_matrix[min._dsti][i] != MAX_W && Y[i])    //有连通,并且是Y集合中的顶点
				{
					minque.push(Edge(min._dsti, i, _matrix[min._dsti][i]));
				}
			}
		}
	}
	//如果该图不是连通图,则没有最小生成树
	if (size < n - 1)
	{
		return W();
	}
	return total_W;
}

测试用例

void TestGraphMinTree()
{
	const char str[] = "abcdefghi";
	matrix::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);
	/*matrix::Graph<char, int> kminTree;
	cout << "Kruskal:" << g.Kruskal(kminTree) << endl;
	kminTree.Print();*/
	cout << "prim算法的实现" << endl;
	matrix::Graph<char, int> pminTree;
	cout << "Prim:" << g.Prim(pminTree, 'a') << endl;
	pminTree.Print();
}

最后细心的你可以会发现,两种算法的结果权值都是37,但是其生成的最小生成树是不同的,这就好比一个正三角形,三个顶点所在的位置,无论连接哪条线,其都是最小生成树,所以不唯一。

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

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

相关文章

OceanBase 里的 schema 是什么?

李博洋 OceanBase 技术部研发工程师。 OceanBase 开源社区里经常会看到一些类似于 “ schema 是什么” 的疑问&#xff1a; 很多同学经常会误以为在 OceanBase 里&#xff0c;schema 只是 database 的同义词&#xff0c;这次分享就从 schema 是什么这个问题稍微展开聊一下。 首…

【51单片机实验笔记】声学篇(一) 蜂鸣器基本控制

目录 前言硬件介绍PWM基础蜂鸣器简介 原理图分析蜂鸣器驱动电路 软件实现蜂鸣器短鸣蜂鸣器功能封装 总结 前言 蜂鸣器在生活中的应用实则相当广泛。通过本章你将学会制造噪声 &#xff08;笑~&#xff09;你将学会驱动它们&#xff0c;并发出响声。 硬件介绍 PWM基础 占空比…

计算机竞赛 基于深度学习的人脸专注度检测计算系统 - opencv python cnn

文章目录 1 前言2 相关技术2.1CNN简介2.2 人脸识别算法2.3专注检测原理2.4 OpenCV 3 功能介绍3.1人脸录入功能3.2 人脸识别3.3 人脸专注度检测3.4 识别记录 4 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于深度学习的人脸专注度…

【CPU】常见术语解释

interrupt service routine&#xff0c;ISR&#xff1a;中断服务程序。 中断&#xff1a;指当CPU正在处理某件事情时&#xff0c;外部发生的某一事件&#xff08;如一个电平的变化&#xff0c;一个脉冲沿的发生或 定时器计数溢出等&#xff09;请求CPU迅速去处理&#xff0c;于…

select多选回显问题 (取巧~)

要实现的效果&#xff1a; 实际上select选择框&#xff0c;我想要的是数组对象&#xff0c;但是后端返回来的是个字符串。 以下是解决方法&#xff1a; 以上是一种简单的解决方法~ 也可以自己处理数据。

【聚类】DBCAN聚类

OPTICS是基于DBSCAN改进的一种密度聚类算法&#xff0c;对参数不敏感。当需要用到基于密度的聚类算法时&#xff0c;可以作为DBSCAN的一种替代的优化方案&#xff0c;以实现更优的效果。 原理 基于密度的聚类算法&#xff08;1&#xff09;——DBSCAN详解_dbscan聚类_root-ca…

分类算法系列⑥:随机森林

目录 集成学习方法之随机森林 1、集成学习方法 2、随机森林 3、随机森林原理 为什么采用BootStrap抽样 为什么要有放回地抽样 4、API 5、代码 代码解释 结果 6、随机森林总结 &#x1f343;作者介绍&#xff1a;双非本科大三网络工程专业在读&#xff0c;阿里云专家…

Llama-7b-hf和vicuna-7b-delta-v0合并成vicuna-7b-v0

最近使用pandagpt需要vicuna-7b-v0&#xff0c;重新过了一遍&#xff0c;前段时间部署了vicuna-7b-v3&#xff0c;还是有不少差别的&#xff0c;transforms和fastchat版本更新导致许多地方不匹配&#xff0c;出现很多错误&#xff0c;记录一下。 更多相关内容可见Fastchat实战…

Python小知识 - 【Python】如何使用Pytorch构建机器学习模型

【Python】如何使用Pytorch构建机器学习模型 机器学习是人工智能的一个分支&#xff0c;它的任务是在已有的数据集上学习&#xff0c;最终得到一个能够解决新问题的模型。Pytorch是一个开源的机器学习框架&#xff0c;它可以让我们用更少的代码构建模型&#xff0c;并且可以让模…

docker 安装rabbitmq

前提&#xff1a;安装好docker docker安装_Steven-Russell的博客-CSDN博客 centos7安装docker_centos7 docker 安装软件_Steven-Russell的博客-CSDN博客 1、启动docker systemctl start docker 2、下载镜像 // 可以先search查询一下可用镜像&#xff0c;此处直接下载最新版本…

LinkedList(3):并发异常

1 LinkedList并发异常 package com.example.demo;import java.util.Iterator; import java.util.LinkedList;public class TestLinkedList {public static void main(String[] args) {LinkedList linkedList new LinkedList(); //双向链表linkedList.add(11);linkedList.add(…

【ES6】require、export和import的用法

在JavaScript中&#xff0c;require、export和import是Node.js的模块系统中的关键字&#xff0c;用于处理模块间的依赖关系。 1、require&#xff1a;这是Node.js中引入模块的方法。当你需要使用其他模块提供的功能时&#xff0c;可以使用require关键字来引入该模块。例如&…

docker从零部署jenkins保姆级教程

jenkins&#xff0c;基本是最常用的持续集成工具。在实际的工作中&#xff0c;后端研发一般没有jenkins的操作权限&#xff0c;只有一些查看权限&#xff0c;但是我们的代码是经过这个工具构建出来部署到服务器的&#xff0c;所以我觉着有必要了解一下这个工具的搭建过程以及简…

分布式环境下的数据同步

一般而言elasticsearch负责搜索&#xff08;查询&#xff09;&#xff0c;而sql数据负责记录&#xff08;增删改&#xff09;&#xff0c;elasticsearch中的数据来自于sql数据库&#xff0c;因此sql数据发生改变时&#xff0c;elasticsearch也必须跟着改变&#xff0c;这个就是…

数据结构与算法-插入希尔归并

一&#xff1a;排序引入 我们通常从哪几个方面来分析一个排序算法&#xff1f; 1.时间效率&#xff1a;决定了算法运行多久&#xff0c;O&#xff08;1&#xff09; 2.空间复杂度&#xff1a; 3.比较次数&交换次数:排序肯定会牵涉到两个操作&#xff0c;一个比较是肯定的。…

mac常见问题(五) Mac 无法开机

在mac的使用过程中难免会碰到这样或者那样的问题&#xff0c;本期为您带来Mac 无法开机怎么进行操作。 1、按下 Mac 上的电源按钮。每台 Mac 电脑都有一个电源按钮&#xff0c;通常标有电源符号 。然后检查有没有通电迹象&#xff0c;例如&#xff1a; 发声&#xff0c;例如由风…

springmvc5.x-mvc实现原理及源码实现

上文&#xff1a;spring5.x-声明式事务原理及源码实现 系列文章&#xff1a; spring5.x-声明式事务原理及源码实现 spring5.x-AOP实现原理及源码分析 spring5.x-监听器原理及源码实现 spring5.x-解决循环依赖分析 spring5.x-IOC模块源码学习 spring5.x介绍及搭配spring源码阅读…

Xcode 清空最近打开的项目

打开Xcode任意项目 File -> Open Recent -> Clear Menu

桌面应用小程序,一种创新的跨端开发方案

Qt Group在提及2023年有桌面端应用程序开发热门趋势时&#xff0c;曾经提及三点&#xff1a; 关注用户体验&#xff1a;无论您是为桌面端、移动端&#xff0c;还是为两者一起开发应用程序&#xff0c;有一点是可以确定的&#xff1a;随着市场竞争日益激烈&#xff0c;对产品的期…

怎么批量在图片名后加相同的文字

怎么批量在图片名后加相同的文字&#xff1f;有个小伙伴通过私信想我咨询一个问题&#xff0c;它从事的是摄影类的工作&#xff0c;每天会在电脑上存储非常多的图片&#xff0c;时间一久电脑上保存的图片非常的多&#xff0c;这让图片的管理和查找变得比较麻烦&#xff0c;有时…