最小生成树,贪心算法和Prim算法的Java代码实现过程详解

news2024/9/24 21:27:15

1.最小生成树原理

之前学习的加权图,我们发现它的边关联了一个权重,那么我们就可以根据这个权重解决最小成本问题,但如何才能找到最小成本对应的顶点和边呢?最小生成树相关算法可以解决。
定义:
图的生成树是它的一棵含有其所有顶点的无环连通子图,一副加权无向图的最小生成树它的一棵权值(树中所有边的权重之和)最小的生成树
在这里插入图片描述
约定
只考虑连通图。最小生成树的定义说明它只能存在于连通图中,如果图不是连通的,那么分别计算每个连通图子图的最小生成树,合并到一起称为最小生成森林。
在这里插入图片描述
所有边的权重都各不相同。如果不同的边权重可以相同,那么一副图的最小生成树就可能不唯一了,虽然我们的算法可以处理这种情况,但为了好理解,我们约定所有边的权重都各不相同。

1.1 树的性质

在这里插入图片描述

1.2 切分定理

要从一副连通图中找出该图的最小生成树,需要通过切分定理完成。
切分:
将图的所有顶点按照某些规则分为两个非空且没有交集的集合。
横切边:
连接两个属于不同集合的顶点的边称之为横切边。
例如我们将图中的顶点切分为两个集合,灰色顶点属于一个集合,白色顶点属于另外一个集合,那么效果如下:
在这里插入图片描述
切分定理:
在这里插入图片描述

2.贪心算法

贪心算法是计算图的最小生成树的基础算法,它的基本原理就是切分定理,使用切分定理找到最小生成树的一条边,不断的重复直到找到最小生成树的所有边。如果图有V个顶点,那么需要找到V-1条边,就可以表示该图的最小生成树。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3.Prim算法

我们学习第一种计算最小生成树的方法叫Prim算法,它的每一步都会为一棵生成中的树添加一条边。一开始这棵树只有一个顶点,然后会向它添加V-1条边,每次总是将下一条连接树中的顶点与不在树中的顶点且权重最小的边加入到树中。
在这里插入图片描述

3.1 API设计

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.2 代码实现

public class PrimMST {
	//索引代表顶点,值表示当前顶点和最小生成树之间的最短边
	private Edge[] edgeTo;
	//索引代表顶点,值表示当前顶点和最小生成树之间的最短边的权重
	private double[] distTo;
	//索引代表顶点,如果当前顶点已经在树中,则值为true,否则为false
	private boolean[] marked;
	//存放树中顶点与非树中顶点之间的有效横切边
	private IndexMinPriorityQueue<Double> pq;
	//根据一副加权无向图,创建最小生成树计算对象
	public PrimMST(EdgeWeightedGraph G) {
		//创建一个和图的顶点数一样大小的Edge数组,表示边
		this.edgeTo = new Edge[G.V()];
		//创建一个和图的顶点数一样大小的double数组,表示权重,并且初始化数组中的内容为无穷大,无穷
		大即表示不存在这样的边
		this.distTo = new double[G.V()];
		for (int i = 0; i < distTo.length; i++) {
			distTo[i] = Double.POSITIVE_INFINITY;
		}
		//创建一个和图的顶点数一样大小的boolean数组,表示当前顶点是否已经在树中
		this.marked = new boolean[G.V()];
		//创建一个和图的顶点数一样大小的索引优先队列,存储有效横切边
		this.pq = new IndexMinPriorityQueue<>(G.V());
		//默认让顶点0进入树中,但0顶点目前没有与树中其他的顶点相连接,因此初始化distTo[0]=0.0
		distTo[0] = 0.0;
		//使用顶点0和权重0初始化pq
		pq.insert(0, 0.0);
		//遍历有效边队列
		while (!pq.isEmpty()) {
		//找到权重最小的横切边对应的顶点,加入到最小生成树中
			visit(G, pq.delMin());
		}
	}
	//将顶点v添加到最小生成树中,并且更新数据
	private void visit(EdgeWeightedGraph G, int v) {
		//把顶点v添加到树中
		marked[v] = true;
		//遍历顶点v的邻接表,得到每一条边Edge e,
		for (Edge e : G.adj(v)) {
		//边e的一个顶点是v,找到另外一个顶点w;
			int w = e.other(v);
		//检测是否已经在树中,如果在,则继续下一次循环,如果不在,则需要修正当前顶点w距离最小生
		成树的最小边edgeTo[w]以及它的权重distTo[w],还有有效横切边也需要修正
			if (marked[w]) {
				continue;
			}
		//如果v-w边e的权重比目前distTo[w]权重小,则需要修正数据
			if (e.weight() < distTo[w]) {
				//把顶点w距离最小生成树的边修改为e
				edgeTo[w] = e;
				//把顶点w距离最小生成树的边的权重修改为e.weight()
				distTo[w] = e.weight();
				//如果pq中存储的有效横切边已经包含了w顶点,则需要修正最小索引优先队列w索引关联的权
				重值
				if (pq.contains(w)) {
					pq.changeItem(w, e.weight());
				} else {
				//如果pq中存储的有效横切边不包含w顶点,则需要向最小索引优先队列中添加v-w和其
				权重值
					pq.insert(w, e.weight());
				}
			}
		}
	}
	//获取最小生成树的所有边
	public Queue<Edge> edges() {
		//创建队列
		Queue<Edge> edges = new Queue<>();
		//遍历edgeTo数组,找到每一条边,添加到队列中
		for (int i = 0; i < marked.length; i++) {
			if (edgeTo[i]!=null){
				edges.enqueue(edgeTo[i]);
			}
		}
		return edges;
		}
	}
	//测试代码
	public class PrimTest {
		public static void main(String[] args) throws Exception {
		//创建输入流
		BufferedReader reader = new BufferedReader(new
		InputStreamReader(PrimTest.class.getClassLoader().getResourceAsStream("min_create_tree_test
		.txt")));
		//读取顶点数目,初始化EdgeWeightedGraph图
		int number = Integer.parseInt(reader.readLine());
		EdgeWeightedGraph G = new EdgeWeightedGraph(number);
		//读取边的数目
		int edgeNumber = Integer.parseInt(reader.readLine());
		//循环读取每一条边,并调用addEdge方法
		for (int i = 0; i < edgeNumber; i++) {
			String line = reader.readLine();
			int v = Integer.parseInt(line.split(" ")[0]);
			int w = Integer.parseInt(line.split(" ")[1]);
			double weight = Double.parseDouble(line.split(" ")[2]);
			G.addEdge(new Edge(v, w, weight));
		}
		//构建PrimMST对象
		PrimMST mst = new PrimMST(G);
		//获取最小生成树的边
		Queue<Edge> edges = mst.edges();
		//打印输出
		for (Edge edge : edges) {
			if (edge!=null){
				System.out.println(edge.either() + "-" + edge.other(edge.either()) + "::" +
				edge.weight());
			}
		}
	}
}

4.kruskal算法

kruskal算法是计算一副加权无向图的最小生成树的另外一种算法,它的主要思想是按照边的权重(从小到大)处理它们,将边加入最小生成树中,加入的边不会与已经加入最小生成树的边构成环,直到树中含有V-1条边为止。
kruskal算法和prim算法的区别:
Prim算法是一条边一条边的构造最小生成树,每一步都为一棵树添加一条边。
kruskal算法构造最小生成树的时候也是一条边一条边地构造,但它的切分规则是不一样的。它每一次寻找的边会连接一片森林中的两棵树。如果一副加权无向图由V个顶点组成,初始化情况下每个顶点都构成一棵独立的树,则V个顶点对应V棵树,组成一片森林,kruskal算法每一次处理都会将两棵树合并为一棵树,直到整个森林中只剩一棵树为止。
在这里插入图片描述

4.1 API设计

在这里插入图片描述

在设计API的时候,使用了一个MinPriorityQueue pq存储图中所有的边,每次使用pq.delMin()取出权重最小的边,并得到该边关联的两个顶点v和w,通过uf.connect(v,w)判断v和w是否已经连通,如果连通,则证明这两个顶点在同一棵树中,那么就不能再把这条边添加到最小生成树中,因为在一棵树的任意两个顶点上添加一条边,都会形成环,而最小生成树不能有环的存在,如果不连通,则通过uf.connect(v,w)把顶点v所在的树和顶点w所在的树
合并成一棵树,并把这条边加入到mst队列中,这样如果把所有的边处理完,最终mst中存储的就是最小生树的所有边。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.2 代码实现

public class KruskalMST {
	//保存最小生成树的所有边
	private Queue<Edge> mst;
	//索引代表顶点,使用uf.connect(v,w)可以判断顶点v和顶点w是否在同一颗树中,使用uf.union(v,w)可以
	把顶点v所在的树和顶点w所在的树合并
	private UF_Tree_Weighted uf;
	//存储图中所有的边,使用最小优先队列,对边按照权重进行排序
	private MinPriorityQueue<Edge> pq;
	//根据一副加权无向图,创建最小生成树计算对象
	public KruskalMST(EdgeWeightedGraph G) {
		//初始化mst队列
		this.mst = new Queue<Edge>();
		//初始化并查集对象uf,容量和图的顶点数相同
		this.uf = new UF_Tree_Weighted(G.V());
		//初始化最小优先队列pq,容量比图的边的数量大1,并把图中所有的边放入pq中
		this.pq = new MinPriorityQueue<>(G.E()+1);
		for (Edge edge : G.edges()) {
			pq.insert(edge);
		}
		
		//如果优先队列pq不为空,也就是还有边未处理,并且mst中的边还不到V-1条,继续遍历
		while (!pq.isEmpty() && mst.size() < G.V() - 1) {
			//取出pq中权重最小的边e
			Edge e = pq.delMin();
			//获取边e的两个顶点v和w
			int v = e.either();
			int w = e.other(v);
			/*
			通过uf.connect(v,w)判断v和w是否已经连通,
			如果连通:
			则证明这两个顶点在同一棵树中,那么就不能再把这条边添加到最小生成树中,因为在一棵
			树的任意两个顶点上添加一条边,都会形成环,
			而最小生成树不能有环的存在;
			如果不连通:
			则通过uf.connect(v,w)把顶点v所在的树和顶点w所在的树合并成一棵树,并把这条边加入
			到mst队列中
			*/
			if (uf.connected(v,w)){
				continue;
			}
			uf.union(v,w);
			mst.enqueue(e);
		}
	}
		//获取最小生成树的所有边
		public Queue<Edge> edges() {
			return mst;
		}
	}
	//测试代码
	public class KruskalTest {
		public static void main(String[] args) throws Exception {
		//创建输入流
		BufferedReader reader = new BufferedReader(new
		InputStreamReader(KruskalTest.class.getClassLoader().getResourceAsStream("min_create_tree_te
		st.txt")));
		//读取顶点数目,初始化EdgeWeightedGraph图
		int number = Integer.parseInt(reader.readLine());
		EdgeWeightedGraph G = new EdgeWeightedGraph(number);
		//读取边的数目
		int edgeNumber = Integer.parseInt(reader.readLine());
		//循环读取每一条边,并调用addEdge方法
		for (int i = 0; i < edgeNumber; i++) {
			String line = reader.readLine();
			int v = Integer.parseInt(line.split(" ")[0]);
			int w = Integer.parseInt(line.split(" ")[1]);
			double weight = Double.parseDouble(line.split(" ")[2]);
			G.addEdge(new Edge(v, w, weight));
		}
		//构建PrimMST对象
		KruskalMST mst = new KruskalMST(G);
		//获取最小生成树的边
		Queue<Edge> edges = mst.edges();
		//打印输出
		for (Edge edge : edges) {
			if (edge!=null){
			System.out.println(edge.either() + "-" + edge.other(edge.either()) + "::" +
			edge.weight());
		}
		}
	}
}

参考:黑马程序员Java数据结构与java算法

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

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

相关文章

新生活、新成长、新认知

总览&#xff1a; 承接上文&#xff1a;https://blog.csdn.net/weixin_46141936/article/details/125537093 ​ 今年夏天 (即大三暑假) 入职 北京金山云 开始进行暑期实习&#xff0c;首次进到大城市、步入职场、接触到各种人、年龄的成长 等等&#xff0c;让我的心态 在 202…

Canvas drawImage() 方法实现图片压缩

图片压缩原理 1.CanvasRenderingContext2D.drawImage() 方法可以从页面 DOM 元素作为图像源来根据坐标和大小重新绘制该图像。 2.HTMLCanvasElement.toDataURL() 和 HTMLCanvasElement.toBlob() 方法支持导出为 base64 字符串或 Blob 对象。 CanvasRenderingContext2D.drawIm…

uboot增加开机logo

uboot的开机logo图片是存放在uboot源码的tools/logos下的&#xff0c;并且对图片的格式是有要求的&#xff0c;必须为bmp格式&#xff0c;且色彩深度为8bit&#xff0c;大小应小于显示屏像素大小。按如下步骤即可在uboot中添加自己的开机logo 1、修改logo的图片格式&#xff0…

Trie树,并查集的简单应用(AcWing)

Trie树 Trie 树&#xff0c;也叫“字典树”。顾名思义&#xff0c;它是一个树形结构。它是一种专门处理字符串匹配的数据结构&#xff0c;用来解决在一组字符串集合中快速查找某个字符串的问题。 在每一个单词的结尾需要进行标记&#xff0c;统计个数 现在对上述样例进行模拟…

C/C++教程-从一个main函数带你走进C、C++的世界

通过文章C语言教程-main函数 我们知道一个程序只有一个main函数; 通过这个文章,学习第一个程序; 程序: #include <stdio.h> int main() {printf("my first programe");return 0; } 输出效果: 很多书籍中的第一个程序都是什么"hello world"…

BurpSuite抓取https数据包配置

今天继续给大家介绍渗透测试相关知识&#xff0c;本文主要内容是BurpSuite抓取https数据包配置。 免责声明&#xff1a; 本文所介绍的内容仅做学习交流使用&#xff0c;严禁利用文中技术进行非法行为&#xff0c;否则造成一切严重后果自负&#xff01; 再次强调&#xff1a;严禁…

Linux 系统安装 pyenv 简明教程

推荐关注博主的微信公众号 Android安全工程&#xff0c;微信公众号围绕 Android 应用的安全防护和逆向分析为主要的两个点&#xff0c; 分享各种安全攻防手段、Hook 技术、ARM 汇编等 Android 相关的知识 前置条件 gitKali 2022 / Ubuntu 16.04 安装步骤 1. 从远程仓库中克隆…

(十三)大白话一行数据中的多个NULL字段值在磁盘上怎么存储?

文章目录 1、为什么一行数据里的NULL值不能直接存储?2、NULL值是以二进制bit位来存储的3、结合小小案例来思考一行数据的磁盘存储格式4、磁盘上的一行数据到底如何读取出来的?5、今日思考题1、为什么一行数据里的NULL值不能直接存储? 之前我们已经给大家讲了在数据库里一行…

【虚幻引擎UE】UE5 按自定义日期范围筛选数据UI实现

基于UMG_Calendar插件按自定义日期范围筛选数据UI实现 数据一般由后端筛选,本文不作具体说明。 如果需要了解快捷 选择本周范围 以及 数据在前端筛选 如何实现,可私聊咨询。 一、插件下载 官方文档 二、范围筛选界面效果实现 1、打开DEMO关卡,默认是简单日历组件 2、创建…

Apollo浅解3

目录 Namespace Item Namespace与Item Namespace修改 界面操作 存储逻辑 更新Item 创建Item 删除Item Namespace发布 界面操作 存储逻辑 发布版本 发布顺序 题外 Namespace Namespace是配置项的集合&#xff0c;类似于一个配置文件的概念。官网解释的更为全面&a…

01 flv 的 binary 解析

想要看一下 这个 flv 的格式主要因素为 rtsp视频服务 转换为 rtmp服务 转换为前端可用的服务 , 然后 里面有 flv.js 的代码, 因为之前出现了一些问题 flvjs 播放 ws 服务代理的不存在的 rtsp 连接, Cannot read properties of null (reading ‘flushStashedSamples‘) 然后看了…

Codeforces Round #664 (Div. 2) C. Boboniu and Bit Operations

Problem - C - Codeforces 翻译&#xff1a; Boboniu喜欢位操作。他想和你玩个游戏。 Boboniu给你两个序列的非负整数&#x1d44e;1&#x1d44e;2,…,&#x1d44e;&#x1d45b;&#x1d44f;1,&#x1d44f;2,…,&#x1d44f;&#x1d45a;。 为每个&#x1d456;(1≤&…

FPGA模拟SENSOR,MIPI CSI-2发送图像到RV1126

FPGA模拟SENSOR&#xff0c;MIPI CSI-2发送图像到RV1126 1&#xff1a;FPGA模拟Sensor&#xff0c; 使用MIPI CSI-2 Transmit&#xff0c;发送图像到RV1126。 2&#xff1a;MIPI CSI-2信号LANE&#xff0c;共5对。图像分辨率1920*1080&#xff0c;格式YUV422 8Bit&#xff0c…

整理了上千个 Python 工具库,涵盖24个大方向

Python 生态&#xff0c;向来以各种类库齐全而闻名&#xff0c;这也是这门语言如此受欢迎的重要原因。 今天就给大家分享一下这几天的战果&#xff0c;宵衣旰食&#xff0c;不眠不休的整理了近千个 Python 库&#xff0c;梳理不易啊&#xff0c;收藏的同时&#xff0c;记得点赞…

数据可视化之seaborn绘图

记录一个今天画出来的数据统计图(绝美&#xff0c;当然数据是癌症相关的就不是很美了, 之前一直都用plt.plot&#xff0c;也不太会用 但是现在发现seaborn真的可以 palette sns.color_palette("ocean", 2) sns.kdeplot(datacancer_data,xRadius (worst),shadeTrue…

ext4 extent详解2之内核源码详解

在查看本文前&#xff0c;希望先查看《ext4 extent详解1之示意图演示》这篇文章&#xff0c;有助于理解本文。本文内核源码版本3.10.96&#xff0c;详细内核详细源码注释见https://github.com/dongzhiyan-stack/kernel-code-comment。 什么时候会用到ext4 extent B树呢&#xf…

SQL SERVER 2016安装部署

1&#xff1a;运行SETUP开始安装SQL SERVER 2016。 2&#xff1a;在计划中可以查看支持SQL SERVER 2016安装的软硬件条件等信息。 3&#xff1a;在左侧安装中选择全新SQL Server独立安装或向现有安装添加功能。 4&#xff1a;可以安装指定的版本&#xff0c;Evaluation这个需要…

圣诞节制作一颗HTML的圣诞树吧

目录 前言&#xff1a; 一、vscode里面运行 二、记事本txt保存运行 前言&#xff1a; 来到圣诞节了&#xff0c;那么就可以制作一颗HTML的圣诞树送给朋友&#xff0c;没有编程基础的小白也可以按照步骤操作也可以运行起来代码的 一、vscode里面运行 1.打开vscoede&#x…

Cadence每日一学_13 | 使用 Allegro 制作PCB封装(以STM32为例)

文章目录一、数据手册分析二、新建封装文件Step1. 新建Package SymbolStep2. 设置设计参数Step3. 设置焊盘路径三、放置焊盘四、绘制Place Bound Top五、绘制装配层六、绘制丝印1. 添加芯片边框丝印2. 添加1脚标识3. 添加芯片位号标识一、数据手册分析 焊盘长度&#xff1a;理论…

Github高效搜索方式

Github高效搜索方式 文章目录Github高效搜索方式0、写在前面1、常用的搜索功能1.1 直接搜索1.2 寻找指定用户|大小的仓库1.3 搜索仓库1.4 查找特定star范围的仓库1.5 查找指定主题1.6 查找仓库语言1.7 搜索issue和pull请求2、 傻瓜式搜索3、参考0、写在前面 名词解释 watch&…