【数据结构】图的最小生成树



快乐的流畅:个人主页


个人专栏:《C游记》《进击的C++》《Linux迷航》

远方有一堆篝火,在为久候之人燃烧!

文章目录

  • 引言
  • 一、最小生成树的概念
  • 二、Kruskal算法
    • 2.1 思想
    • 2.2 实现
  • 三、Prim算法
    • 3.1 思想
    • 3.2 实现
  • 四、Kruskal和Prim的对比

引言

前置知识:【数据结构】并查集
前置知识:【数据结构】图的概念和存储结构

一、最小生成树的概念

最小生成树(Minimum Spanning Tree, MST) 是图论中的一个重要概念,指的是在一个连通的无向图中,选择一部分边,使得这些边连接所有顶点且边权值之和最小,同时生成的子图仍是一个树结构(没有环)。


按照定义,若连通网络由n个顶点组成,则其生成树必含n个顶点,n-1条边。因此,构造最小生成树有3个准则:

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


下面讲解两种经典的最小生成树算法——kruskal算法和prim算法,它们都采用了贪心策略来进行实现。

前置声明:

template<class W>
struct Edge
{
	int _srci;
	int _dsti;
	W _w;

	Edge(int srci, int dsti, const W& w)
		: _srci(srci)
		, _dsti(dsti)
		, _w(w)
	{}

	bool operator>(const Edge& e) const
	{
		return _w > e._w;
	}
};

template<class V, class W, W W_MAX = INT_MAX, bool Direction = false>
class Graph
{
	typedef Edge<W> Edge;
	typedef Graph<V, W, W_MAX, Direction> Self;
	//...
}

二、Kruskal算法

2.1 思想

Kruskal算法采用全局贪心的策略:

  1. 每次选取图中权值最小的边。
  2. 每次选取完后,判断是否构成回路。若构成,则舍弃这条边。

具体图示如下:

2.2 实现

思路:

  1. 采用优先级队列(小堆),将所有边存入,以便每次选取全图权值最小的边。
  2. 采用并查集,存储已选取的顶点。每次选取边时,判断两侧顶点是否在并查集中,以此判断是否构成回路。
W Kruskal(Self& minTree)
{
	minTree._vertexs = _vertexs;
	minTree._indexMap = _indexMap;
	int n = _edges.size();
	minTree._edges.resize(n, vector<W>(n, W_MAX));

	priority_queue<Edge, vector<Edge>, greater<Edge>> minHeap;
	for (int i = 0; i < n; ++i)
	{
		for (int j = 0; j < n; ++j)
		{
			if (i < j && _edges[i][j] != W_MAX)//无向图,防止重复记录路径
			{
				minHeap.push(Edge(i, j, _edges[i][j]));
			}
		}
	}

	UnionFindSet<V> ufs(n);
	W total = W();
	int count = 0;
	while (!minHeap.empty())
	{
		Edge top = minHeap.top();
		minHeap.pop();

		int srci = top._srci, dsti = top._dsti;
		W w = top._w;
		if (!ufs.InSet(srci, dsti))
		{
			minTree._AddEdge(srci, dsti, w);
			ufs.Union(srci, dsti);
			total += w;
			++count;

			if (count == n - 1)
			{
				break;
			}

			cout << _vertexs[srci] << "--" << _vertexs[dsti] << ":" << w << endl;
		}
		else
		{
			cout << _vertexs[srci] << "--" << _vertexs[dsti] << ":" << w << "[构成环]" << endl;
		}
	}

	if (count == n - 1)
	{
		return total;
	}
	else
	{
		return W();
	}
}

细节:

  1. 输入一个空图,输出最小生成树的总权值,并将空图变为最小生成树。
  2. count记录已选边数,若达到n-1,则可直接跳出循环,提高效率。

三、Prim算法

3.1 思想

Prim算法采用局部贪心的策略:

  1. 将已被选择的点看作一个顶点集合,初始状态只有起点在集合中。
  2. 每次在集合周围查找,找到消耗权值最小即可抵达的点,并将其纳入集合。

具体图示如下:

3.2 实现

思路:

  1. 采用优先级队列(小堆),将起始点周围的路径存入,以便每次选取集合周围权值最小的边。
  2. 集合用bool数组表示,每次只有当目标点不在集合中,才将其纳入集合。
  3. 每次将新点纳入集合后,再将新点周围的路径存入优先级队列,依次迭代。
W Prim(Self& minTree, const V& src)
{
	minTree._vertexs = _vertexs;
	minTree._indexMap = _indexMap;
	int n = _edges.size();
	minTree._edges.resize(n, vector<W>(n, W_MAX));

	//起始点周围的路径入堆
	priority_queue<Edge, vector<Edge>, greater<Edge>> minHeap;
	int srci = GetIndex(src);
	for (int i = 0; i < n; ++i)
	{
		if (_edges[srci][i] != W_MAX)
		{
			minHeap.push(Edge(srci, i, _edges[srci][i]));
		}
	}

	vector<bool> S(n, false);
	S[srci] = true;

	W total = W();
	int count = 0;
	while (!minHeap.empty())
	{
		Edge top = minHeap.top();
		minHeap.pop();

		int srci = top._srci, dsti = top._dsti;
		W w = top._w;
		if (!S[dsti])
		{
			minTree._AddEdge(srci, dsti, w);
			S[dsti] = true;
			total += w;
			++count;

			if (count == n - 1)
			{
				break;
			}

			for (int i = 0; i < n; ++i)
			{
				if (!S[i] && _edges[dsti][i] != W_MAX)//无向图,防止重复记录路径
				{
					minHeap.push(Edge(dsti, i, _edges[dsti][i]));
				}
			}

			cout << _vertexs[srci] << "--" << _vertexs[dsti] << ":" << w << endl;
		}
		else
		{
			cout << _vertexs[srci] << "--" << _vertexs[dsti] << ":" << w << "[构成环]" << endl;
		}
	}

	if (count == n - 1)
	{
		return total;
	}
	else
	{
		return W();
	}
}

细节:

  1. 输入一个空图和一个起始点,输出最小生成树的总权值,并将空图变为最小生成树。
  2. count记录已选边数,若达到n-1,则可直接跳出循环,提高效率。

四、Kruskal和Prim的对比

Kruskal 算法Prim 算法
算法类型贪心算法贪心算法
适用图类型稀疏图,特别适合边权值分布较广的图稠密图,特别适合顶点多边多的图
基本思想从边的角度出发,按权值从小到大选择边从顶点出发,每次扩展最小权值的边
时间复杂度O(E log V)O(E log V)
典型应用网络设计问题,边多且分散的图电网、网络设计问题,稠密的图
贪心选择标准每次选择权值最小且不形成环的边每次选择最小的连接权值,扩展已加入的顶点集合

  • Kruskal:适用于稀疏图,因为其从边的角度出发,边的数量相对少时效率更高。
  • Prim:适用于稠密图,因为它每次从顶点出发,逐渐扩展树,对于稠密图(边多的图)效率更高

真诚点赞,手有余香

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

container_of 函数的分析

这个函数的目的是&#xff0c; 通过结构体里面的内容 找到 大结构体的 基地址。 函数的原型是&#xff1a;  &#xff30;&#xff34;&#xff32;是指针 &#xff54;&#xff59;&#xff50;&#xff45; &#xff0c; &#xff4d;&#xff45;&#xff4d;&#xff…

新手上路:Anaconda虚拟环境创建和配置以使用PyTorch和DGL

文章目录 前言步骤 1: 安装 Anaconda步骤 2: 创建新的 Anaconda 环境步骤 3: 安装最新版本的 PyTorch步骤 4: 安装特定版本的 PyTorch步骤 5: 安装最新版本的 DGL步骤 6: 安装特定版本的 DGL步骤 7: Pycharm中使用虚拟环境解释器第一种情况&#xff1a;创建新项目第二种情况&am…

Word办公自动化的一些方法

1.Word部分内容介绍 word本身是带有格式的一种文档&#xff0c;有人说它本质是XML&#xff0c;所以一定要充分利用标记了【样式】的特性来迅速调整【格式】&#xff0c;从而专心编辑文档内容本身。 样式&#xff08;集&#xff09; 编号&#xff08;多级关联样式编号&#xff…

Tomcat系列漏洞复现

CVE-2017-12615——Tomcat put⽅法任意⽂件写⼊漏洞 漏洞描述 当 Tomcat运⾏在Windows操作系统时&#xff0c;且启⽤了HTTP PUT请求⽅法&#xff08;例如&#xff0c;将 readonly初始化参数由默认值设置为false&#xff09;&#xff0c;攻击者将有可能可通过精⼼构造的攻击请求…

探索 Snowflake 与 Databend 的云原生数仓技术与应用实践 | Data Infra NO.21 回顾

上周六&#xff0c;第二十一期「Data Infra 研究社」在线上与大家相见。活动邀请到了西门子数据分析师陈砚林与 Databend 联合创始人王吟&#xff0c;为我们带来了一场关于 Snowflake 和 Databend 的技术探索。Snowflake&#xff0c;这个市值曾超过 700 亿美元的云原生数据仓库…

Android 安卓内存安全漏洞数量大幅下降的原因

谷歌决定使用内存安全的编程语言 Rust 向 Android 代码库中写入新代码&#xff0c;尽管旧代码&#xff08;用 C/C 编写&#xff09;没有被重写&#xff0c;但内存安全漏洞却大幅减少。 Android 代码库中每年发现的内存安全漏洞数量&#xff08;来源&#xff1a;谷歌&#xff09…

资质申请中常见的错误有哪些?

在申请建筑资质的过程中&#xff0c;企业可能会犯一些常见的错误&#xff0c;以下是一些需要避免的错误&#xff1a; 1. 资料准备不充分&#xff1a; 申请资质需要提交大量的资料&#xff0c;包括企业法人资料、财务报表、业绩证明等。资料不齐全或不准确都可能导致申请失败。…

汽车线束之故障诊断方案-TDR测试

当前&#xff0c;在汽车布局中的线束的性能要求越来越高。无法通过简单的通断测试就能满足性能传输要求。早起对智能化要求不高&#xff0c;比如没有激动雷达、高清摄像、中央CPU等。 近几年的智能驾驶对网络传输要求越来越高&#xff0c;不但是高速率&#xff0c;还需要高稳定…

【C++题目】7.双指针_和为 s 的两个数字

文章目录 题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a;图解 题目链接&#xff1a; LCR 179.查找总价格为目标值的两个商品 题目描述&#xff1a; 解法 解法一&#xff08;暴力解法&#xff0c;会超时&#xff09; 两层 for 循环列出所有两个数字的组合…

一种使用 SUMO + Python 联合仿真平台

一种使用 SUMO Python 联合仿真平台&#xff08;一&#xff09; 本文适用人群包括但不仅限于【交通运输】【车辆工程】【自动化控制】【计算机科学与技术】等专业本科生、研究生、博士生。本文通过在Pycharm平台&#xff0c;使用Python语言 Traci工具包&#xff0c;调用SUMO客…

【步联科技身份证】 身份证读取与解析———未来之窗行业应用跨平台架构

一、身份证解析代码 C# function 身份证数据解析_湖南步联科技(wzxx) {var result {};result[xm] wzxx.substr(0, 15);result[xbdm] wzxx.substr(15, 1);result[mzdm] wzxx.substr(16, 2);result[csrq] wzxx.substr(18, 8);result[dzmc] wzxx.substr(26, 35);result[gms…

Ansible-template模块动态生成特定文件

文章目录 一、Jinja2介绍什么是主要特性安装基本用法进阶特性总结 Jinja2与Ansible关系1. 模板引擎2. Ansible 的依赖3. 变量和模板4. 动态生成配置5. 社区和生态系统总结 二、Ansible如何使用Jinja2使用template模块Jinja2文件中使用判断和循环Jinja2文件中使用判断语法 Jinja…

如何在算家云搭建text-generation-webui(文本生成)

一、text-generation-webui 简介 text-generation-webui 是一个流行的用于文本生成的 Gradio Web UI。支持 transformers、GPTQ、AWQ、EXL2、llama.cpp (GGUF)、Llama 模型。 它的特点如下&#xff0c; 3 种界面模式&#xff1a;default (two columns), notebook, chat支持多…

Vue发送邮件攻略:从搭建到实现详细步骤?

vue发送邮件功能实现方法&#xff1f;Vue前端如何实现发送邮件&#xff1f; 随着应用功能的不断扩展&#xff0c;用户交互的复杂性也在增加。其中&#xff0c;发送邮件功能是许多Web应用中不可或缺的一部分。AokSend将详细介绍如何使用Vue.js实现发送邮件功能。 Vue发送邮件&…

Springboot指定扫描路径

方式一&#xff1a;通过在启动类的SpringbootApplication中指定包扫描或类扫描 指定需要扫描的包 scanBasePackages{"待扫描包1","待扫描包2", . . . ," "} 指定需要扫描的类 scanBasePackageClasses{类1.class,类2.class,...} 方式二&#xff…

STM32F103C8----3-1 LED闪烁(跟着江科大学STM32)

一&#xff0c;电路图 接线图 面包板的的使用请参考&#xff1a;《面包板的使用_面包板的详细使用方法-CSDN博客》 二&#xff0c;目的/效果 2.1 推婉输出 外部供电&#xff08;熄的时间长&#xff09; 2.2 推婉输出 内部供电(亮的时间长) 三&#xff0c;创建Keil项目 详…

音乐项目总结(终)

总的来说写这个项目还是状态差了&#xff0c;前期中期写太慢&#xff0c;后期疯狂赶。 讲点对写这个项目能想起来解决的问题和写的的感触。 前期&#xff1a;当时觉得时间很充足&#xff0c;有布置算法题&#xff0c;我竟然还花三四天去学算法&#xff0c;&#xff0c;动态规划…

【网络安全】网络基础第一阶段——第三节:网络协议基础---- VLAN、Trunk与三层交换技术

目录 一、交换机 1.1 交换机定义 1.1.1 交换机 1.2 工作原理 1.2.1 数据帧的转发 1.2.2 交换机处理数据帧的三种行为 1.2.3 交换机通信 二、虚拟局域网&#xff08;VLAN&#xff09; 2.1 虚拟局域网简介 2.1.1 为什么需要VLAN 2.1.2 广播域的分割与VLAN的必要性 2.…

FPGA实现PCIE图片采集转HDMI输出,基于XDMA中断架构,提供3套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的PCIE方案 3、PCIE基础知识扫描4、工程详细设计方案工程设计原理框图测试图片QT上位机XDMA配置及使用XDMA中断模块FDMA图像缓存Native视频时序生成RGB转HDMI输出模块Windows版本XDMA驱动安装Linux版本XDMA驱动安装工程源码…

Stable Diffusion绘画 | 来训练属于自己的模型:素材处理与打标篇

纵观整个模型训练流程&#xff0c;图片素材准备和打标环节占据的分量比重&#xff0c;绝对超过60%。 上一篇分享了图片素材准备&#xff0c;这一篇&#xff0c;开始对准备好的图片素材进行处理了。 素材处理 我已经收集了 霉霉 的25张图片&#xff1a; 但是&#xff0c;发现…