最小生成树(Prim算法与Kruskal算法)

news2024/10/7 4:32:56

一、什么是最小生成树

一个连通图的生成树是一个极小的连通子图,它含有图中全部的n个顶点,但只有足以构成一棵树的n-1条边。我们把构造连通网的最小代价生成树称为最小生成树。

例如下图中①、②、③都是左侧图的生成树,但③是构造连通网的最小代价,所以③是该图的最小生成树。

二、Prim算法

Prim算法的核心思想如下:

  • ① 将图中所有顶点分为A、B两类,初始时所有顶点都在B类
  • ② 选择任意一个顶点,将其放入A类
  • ③ 从B类所有顶点出发,找一条连接A类某个顶点且权值最小的边。将这条边连接的B类中的顶点放入A类中。
  • ④ 重复③步骤,直到所有B类中的顶点全部放入A类。

下面通过图来说明最小生成树的构建过程

首先,在初始时,所有顶点都在B类

选择顶点A放入A类中,然后寻找B类到A类权值最小的边。很显然是BA这条边

将顶点B放入A类中,然后继续寻找B类到A类权值最小的边。结果是AE

将顶点E放入A类中,继续寻找。结果是AC

将顶点C放入A类中,继续寻找。结果只有BD

将顶点D加入A类,构建结束。

原理很简单,直接上代码。这里的图采用邻接矩阵存储

// 边结构
struct Edge:IComparable<Edge>
{
    public int from;
    public int to;
    public int weight;
    
    public int CompareTo(Edge other)
    {
        return weight - other.weight;
    }
}

private void Prim<T>(GraphByAdjacencyMatrix<T> graph)
{
	var graphCount = graph.Count;
	// 用来记录遍历过的顶点
	bool[] nodes = new bool[graphCount];
	// 用来记录当前遍历到的边
	Edge[] edges = new Edge[graphCount];

	// 将第一个顶点设置为已遍历
	nodes[0] = true;
	// 将第一个顶点对应的边加入集合
	// 都从1开始遍历是因为n个顶点对应n-1条边
	for (int i = 1; i < graphCount; i++)
	{
		edges[i] = new Edge {from = 0, to = i, weight = graph.Matrix[0, i]};
	}

	for (int i = 1; i < graphCount; i++)
	{
		// 找出权值最小的边
		int min = Int32.MaxValue;
		int minIndex = 0;
		for (int j = 1; j < graphCount; j++)
		{
			if (!nodes[j] && edges[j].weight < min)
			{
				min = edges[j].weight;
				minIndex = j;
			}
		}

		// 将新的顶点加入已遍历集合
		nodes[minIndex] = true;
		// 打印边
		Console.Write($"({edges[minIndex].from},{edges[minIndex].to}) ");

		// 将新的顶点对应的边加入集合
		// 忽略已经访问过的顶点、忽略比当前遍历的边更长的边
		for (int j = 1; j < graphCount; j++)
		{
			if (!nodes[j] && edges[j].weight > graph.Matrix[minIndex, j])
			{
				edges[j] = new Edge {from = minIndex, to = j, weight = graph.Matrix[minIndex, j]};
			}
		}
	}
}

Prim算法关注的是顶点,通过寻找各顶点上权值最小的边,逐步构建起最小生成树。Prim算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2),n为顶点数。因此对于边数非常多的稠密图,Prim算法在性能上会更有优势。

三、Kruskal算法

与Prim算法关注顶点的思路不同,Kruskal算法关注点在于边。它的原理也很简单,就是先将所有的边按权值从小到大进行排序。然后遍历边集,只要遍历到的这条边不会与结果集中的边形成环,就将其加入结果集。

代码如下

private void Kruskal<T>(GraphByAdjacencyMatrix<T> graph)
{
	// 自己实现的小根堆,用来对边排序
	HeapList<Edge> edges = new HeapList<Edge>();
	// 一维数组用来检验是否成环
	int[] parent = new int[graph.Count];
	
	// 将边加入小根堆
	for (int i = 0; i < graph.Count; i++)
	{
		for (int j = i+1; j < graph.Count; j++)
		{
			if(graph.Matrix[i,j] == Int32.MaxValue) continue;
			edges.Push(new Edge(){from = i,to=j,weight = graph.Matrix[i,j]});
		}
	}

	for (int i = 0; i < graph.Count; i++)
	{
		// 弹出权值最小的边
		var edge = edges.Pop();
		int m = Find(parent, edge.from);
		int n = Find(parent, edge.to);
		
		// 如果n!=m,则未形成环路
		if (n != m)
		{
			parent[m] = n;
			// 打印边
			Console.Write($"({edge.from},{edge.to})");
		}
	}
}

/// <summary>
/// 校验是否成环
/// </summary>
private int Find(int[] parent,int index)
{
	while (parent[index] != 0)
	{
		index = parent[index];
	}
	return index;
}

这里的parent[]的作用可能有些难以理解。事实上它相当于一个并查集,用来检验是否成环。我们通过前面的例子来具体说明

首先进行校验的边是 ( 1 , 2 ) (1,2) (1,2),此时parent[]中的元素都为0,因此返回的m = 1,n = 2,因为m != n,所以将parent[1] = 2。这步操作意味着将顶点B(下标为1)和C(下标为2)加入了集合,且集合的代表为C

接下来进行校验的边是 ( 0 , 1 ) (0,1) (0,1)。返回的m = 0,n = 2,所以将parent[0] = 2。即顶点A(下标为0)加入C这个集合

下一条边为 ( 0 , 4 ) (0,4) (0,4),返回的m = 2,n = 4,所以将parent[2] = 4。即将C集合整个加入E所在的集合

接下来是 ( 0 , 2 ) (0,2) (0,2),返回的m = 4,n = 4。此时n == m,意味着两个节点所在的集合都为E集合。也就是说这两个节点本身就是连通的,所以添加这条新的边会使生成树成环,需要舍弃。

接下来是 ( 1 , 3 ) (1,3) (1,3),返回的m = 4,n = 3,所以将parent[4] = 3,即将E集合加入D所在的集合

生成树构建完成,退出循环。

当图的边数为 e e e时,Find()函数的时间复杂度为 l o g e loge loge,外层循环的时间复杂度为 e e e。因此整个算法的时间复杂度为 e l o g e eloge eloge。Kruskal算法对于边数较少的稀疏图在性能上有很大优势。

四、参考资料

[1].《大话数据结构》
[2]. http://c.biancheng.net/algorithm/prim.html

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

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

相关文章

k8s的亲和调度

k8s的亲和调度 出于高效通信等需求&#xff0c;偶尔需要把一些Pod对象组织在相近的位置(同一节点、机架、区域或地区等)&#xff0c;例如应用程序的Pod及其后端提供数据服务的Pod等&#xff0c;我们可以认为这是一类具有亲和关系的Pod对象。 理想的实现方式是允许调度器把第一个…

[附源码]计算机毕业设计springboot高校流浪动物领养网站

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

2022.11.28总结

今天写了条件查询 虽然思路上还说是比较顺&#xff0c;但是还是写了一晚上&#xff0c;因为老是在细节上出现bug&#xff0c;改了好久&#xff0c;踩了好几个坑。 首先大概是因为组件不是确定的&#xff0c;我把ref属性绑定在router-view上&#xff0c;导致我获取不到条件选择…

c++类型转换

目录 1.隐式类型转换和强制类型转换 2.隐式类型转换带来的危险 3.c提供的标准类型转换关键字 4.总结 1.隐式类型转换和强制类型转换 c语言的类型转换可以分为隐式类型转换和强制类型转换。 #include<iostream>using namespace std;int main() {double a 3.14;int …

医疗保健行业的福音是对话式AI吗?

导读对话式AI可以对医疗保健行业产生重大影响&#xff0c;且在许多领域已经产生了影响。如果使用得当&#xff0c;对话式AI可以提高操作效率和临床结果&#xff0c;并减轻医护人员的工作量。 对话式AI技术开启了数字患者护理的新时代。 患者可以随时访问其需要的数据&#xff…

Ubuntu 18.04 + CUDA 11.3.0 + CUDNN 8.2.1 + Anaconda + Pytorch 1.10

Xshell远程连接进行Ubuntu的Pytorch配置写在最前面参考Xshell常用命令Ubantu检查系统的各项配置查看ubuntu系统的版本信息和gcc版本查看Linux的内核版本和系统是多少位的验证机器是否具有n卡各种配置&#xff08;建议不要省略&#xff09;安装vim增加pip镜像源禁用nouveau开启S…

[附源码]计算机毕业设计springboot高校学生摄影作品展示平台

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

app与小程序的区别【开发小程序】

app与小程序&#xff0c;两者有什么区别呢&#xff1f;很多公司在开发app或是开发小程序上比较纠结&#xff0c;二选一不止究竟选哪个好&#xff0c;当然有财力的公司可能会两者都开发。那么下面说说app与小程序的区别是什么&#xff0c;好让大家更好地二选一。 app与小程序的…

单商户商城系统功能拆解39—分销应用—分销等级

单商户商城系统&#xff0c;也称为B2C自营电商模式单店商城系统。可以快速帮助个人、机构和企业搭建自己的私域交易线上商城。 单商户商城系统完美契合私域流量变现闭环交易使用。通常拥有丰富的营销玩法&#xff0c;例如拼团&#xff0c;秒杀&#xff0c;砍价&#xff0c;包邮…

2022小美赛数学建模ABCD赛题思路分析 - 认证杯

一、竞赛信息 考虑到美国大学生数学建模竞赛即将举行&#xff0c;近几年国内院校参加美赛的热情一直比较高涨&#xff0c;去年参赛规模已经突破了30000支队&#xff0c;但是由于美赛需要用英文书写论文&#xff0c;中文和英文的语法和思维差异比较明显&#xff0c;另外美赛参赛…

解决 Android WebView 多进程导致App崩溃

应用场景 应用内有两个位置用到WebView加载页面&#xff0c;具体处理逻辑不能通用。分别扩展了WebView了。应用内独立页面使用Fragment来展示,(采用单Activity架构&#xff09;。应用提供切换语言功能。 问题猜想 一、WebView内核bug 具体路径&#xff1a; 进入app–>设…

[附源码]SSM计算机毕业设计校园征兵及退役复原管理系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

ABAQUS学习之路

ABAQUS入门 首先看模块 1、部件——类似建模 2、属性——给零件添加材料属性 3、装配 4、分析步—— 5、相互作用——创建接触&#xff08;摩擦力&#xff09; 6、载荷——预定义场&#xff08;温度场、力场&#xff09;、边界条件 7、网络——划分网格 8、优化&#…

Libvirt Java API操作QEMU虚拟机(重启,强制关机,挂起,恢复,详情,关机,注销,快照备份等 )(CentOS)

需求背景 有个产品需求&#xff0c;需要在一台linux上装多个虚拟机&#xff0c;然后每个虚拟机单独部署一个产品&#xff0c;然后需要虚拟机的一个产品去控制宿主机中安装虚拟机的状态 注意&#xff1a; 如果虚拟机中装的产品去连宿主机的Libvirt服务&#xff0c;那么虚拟机一…

LeetCode530.二叉搜索树的最小绝对差 501二叉搜索树中的众数 236二叉树的最近公共祖先

文章目录530二叉搜索树的最小绝对差c代码实现python 代码实现501二叉搜索树中的众数c 代码实现python 代码实现236二叉树的最近公共祖先c代码实现python代码实现530二叉搜索树的最小绝对差 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差…

运维行业数字化维修数据屏来袭

说起维修数字化&#xff0c;售后维保管理&#xff0c;大家必然想到青鸟云报修&#xff0c;今天我给大家呈现一下青鸟云报修数据大屏是怎么一回事。 这是青鸟云报修第三代数据大屏&#xff0c;在2代基础上增加了更多板块&#xff0c;更加专业和智能化&#xff0c;他主要应用于单…

Unity ab包加载文本 puerts 自定义loader

输出ab包 他会把你创建的ab包都打包 也就是在这里的创建的 string assetBundleDirectory Path.Combine(Application.streamingAssetsPath, "OutAssetBundles"); if (!Directory.Exists(assetBundleDirectory)) {Directory.CreateDirectory(assetBundleDirectory);…

2022/11/28-29总结

刷题 统计2021年10月每个退货率不大于0.5的商品各项指标_牛客题霸_牛客网 思路 主要就是sum函数、round函数、date_format函数 代码实现 select product_id, round(sum(if_click)/count(*),3) ctr, round(sum(if_cart)/sum(if_click),3) cart_rate, round(sum(if_payment)…

大二学生JavaScript实训大作业——动漫秦时明月7页 期末网页制作 HTML+CSS+JavaScript 网页设计实例 企业网站制作

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 ⚽精彩专栏推荐&#x1…

【JavaScript预解析】

JavaScript预解析1 本节目标2 预解析3 变量预解析和函数预解析4 预解析案例1 本节目标 知道解析器运行JS分为哪两步说出变量提升的步骤和运行过程说出函数提升的步骤和运行过程 2 预解析 JavaScript代码是由浏览器中的JavaScript解析器来执行的。JavaScript解析器在运行Java…