数据结构(13)最小生成树JAVA版:prim算法、kruskal算法

news2024/12/23 14:06:21

目录

13.1.概述

13.2.prim算法

13.2.1.概述

13.2.2.代码实现

13.3.kruskal算法

13.3.1.概述

 13.3.2.代码实现


13.1.概述

最小生成树,包含图的所有顶点的一棵树,树的边采用包含在图中的原有边中权重和最小的边。翻译成人话就是遍历一遍全图所有顶点的最短路径,这条路径就叫最小生成树。

最小生成树存在和图是连通图互为充要条件,顶点都不连通,肯定不可能有路能遍历一遍全图。

求解最小生成树有两种常用算法:

  • prim算法
  • kruskal算法

13.2.prim算法

13.2.1.概述

prim算法和Dijkstra算法过程很像,区别在于Dijkstra算法中dist为当前节点到根节点的距离,prim算法中dist为当前节点到树的距离。Dijkstra算法每次是将离根节点最近的节点纳入,prim每次是将离树最近的节点纳入。

 Dijkstra算法可以参考博主的上篇文章:数据结构(12)Dijkstra算法JAVA版:图的最短路径问题__BugMan的博客-CSDN博客 

13.2.2.代码实现

以遍历下图为例:

public class prim {
    static int[][] graph;
    static int[] dist;
    static int[] path=new int[7];
    static boolean[] isUsed=new boolean[7];
    static {
        graph=new int[][]{
                {0,1,4,3,0,0,0},
                {1,0,3,0,0,0,0},
                {4,3,0,2,1,5,0},
                {3,0,2,0,2,0,0},
                {0,0,1,2,0,0,0},
                {0,0,5,0,0,0,2},
                {0,0,0,0,0,2,0}
        };
        dist=new int[]{Integer.MAX_VALUE,Integer.MAX_VALUE,Integer.MAX_VALUE,Integer.MAX_VALUE,Integer.MAX_VALUE,Integer.MAX_VALUE,Integer.MAX_VALUE};
    }
    public static void prim(){
        while(true){
            //判断节点是否已经全部纳入
            if(isOver()){
                break;
            }
            //寻找未纳入的节点中距离树最近的节点
            int i=findRecently();
            //设置为已遍历状态
            isUsed[i]=true;
            //遍历该节点邻接节点
            for (int j=0;j<graph[i].length;j++) {
                if(graph[i][j]!=0&&isUsed[j]==false){
                    //更新邻接节点的dist、path
                    flashDistAndPath(i,j);
                }
            }
        }
    }

    public static int findRecently(){
        int min=Integer.MAX_VALUE;
        int index=-1;
        for(int i=0;i<dist.length;i++){
            if(min>dist[i]&&isUsed[i]==false){
                min=dist[i];
                index=i;
            }
        }
        return index;
    }

    public static void flashDistAndPath(int i,int j){
        if (graph[i][j] < dist[j]) {
            dist[j] = graph[i][j];
            path[j] = i;
        }
    }

    public static boolean isOver(){
        int trues=0;
        for (boolean isused:isUsed) {
            if(isused==true){
                trues++;
            }
        }
        if(trues==dist.length){
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        isUsed[0]=true;
        dist[1]=1;
        path[1]=0;

        prim();
        for (int i=0;i<dist.length;i++){
            System.out.println(dist[i]);
        }
    }
}

13.3.kruskal算法

13.3.1.概述

kruskal算法,将森林合并成树,过程即使用贪心思想每次将不构成回路的最短边纳入。最后就是将一棵棵小树树组成的森林合成一课大树,即最小生成树。

为什么不构成回路喃,构成回路一定不会是最短路径,这个自行画图思考一下就能明白,或者参照下面例子也能理解。

以下图为例展示kruskal算法的全过程:

先将最小的边(权重为1)的纳入森林:

 接下来将剩余最小的边(权重为2)纳入森林:

 

接下来将剩下最小的边(权重为4)纳入森林,不能纳入权重为3的边,因为纳入后会构成回路。有一条权重为4的边也因为纳入后会构成回路所以不能纳入森林:

这里就可以思考一下如果将构成回路的边纳入森林,会产生什么情况。

同理权重为5的边不能纳入,应该纳入权重为6的边,完成将每个节点纳入树,生成最小生成树:

 13.3.2.代码实现

kruskal的实现偷个懒了,引用站内其他博主的实现:

原文链接:https://blog.csdn.net/weixin_48544279/article/details/126843851

(主要是当时想着kruskal的实现过程不复杂,就偷懒没留下自己的实现代码。哈哈哈~)

private int edgeNum; //边的个数
	private char[] vertexs; //顶点数组
	private int[][] matrix; //邻接矩阵
	//使用 INF 表示两个顶点不能连通
	private static final int INF = Integer.MAX_VALUE;

	public static void main(String[] args) {
		//创建顶点数组
		char[] vertexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
		//图的邻接矩阵(二维数组)
		int matrix[][] = {
				        /*A*//*B*//*C*//*D*//*E*//*F*//*G*/
				/*A*/ {   0,  12, INF, INF, INF,  16,  14},
				/*B*/ {  12,   0,  10, INF, INF,   7, INF},
				/*C*/ { INF,  10,   0,   3,   5,   6, INF},
				/*D*/ { INF, INF,   3,   0,   4, INF, INF},
				/*E*/ { INF, INF,   5,   4,   0,   2,   8},
				/*F*/ {  16,   7,   6, INF,   2,   0,   9},
				/*G*/ {  14, INF, INF, INF,   8,   9,   0}};
		//大家可以在去测试其它的邻接矩阵,结果都可以得到最小生成树.

		//创建KruskalCase 对象实例
		KruskalCase kruskalCase = new KruskalCase(vertexs, matrix);
		
		kruskalCase.kruskal();
	}

	//构造器
	public KruskalCase(char[] vertexs, int[][] matrix) {
		//初始化顶点数和边的个数
		int vlen = vertexs.length;

		//初始化顶点, 复制拷贝的方式
		this.vertexs = new char[vlen];
		for(int i = 0; i < vertexs.length; i++) {
			this.vertexs[i] = vertexs[i];
		}

		//初始化边, 使用的是复制拷贝的方式
		this.matrix = new int[vlen][vlen];
		for(int i = 0; i < vlen; i++) {
			for(int j= 0; j < vlen; j++) {
				this.matrix[i][j] = matrix[i][j];
			}
		}
		//统计边的条数
		for(int i =0; i < vlen; i++) {
			for(int j = i+1; j < vlen; j++) {
				if(this.matrix[i][j] != INF) {
					edgeNum++;
				}
			}
		}

	}
	public void kruskal() {
		int index = 0; //表示最后结果数组的索引
		//用于保存"已有最小生成树" 中的每个顶点在最小生成树中的终点
		//用来判断是否出现回路
		int[] ends = new int[edgeNum];
		//创建结果数组, 保存最后的最小生成树
		EData[] rets = new EData[edgeNum];
		//统计最小生成树的总权值
		int totalWeight = 0;

		//获取图中 所有的边的集合 , 一共有12边
		EData[] edges = getEdges();
		System.out.println("图的边的集合=" + Arrays.toString(edges) + " 共"+ edges.length); //12

		//按照边的权值大小进行排序(从小到大)
		sortEdges(edges);

		//遍历edges 数组,将边添加到最小生成树中时,判断是准备加入的边否形成了回路,如果没有,就加入 rets, 否则不能加入
		for(int i=0; i < edgeNum; i++) {
			//获取到第i条边的第一个顶点(起点)
			int p1 = getPosition(edges[i].start);
			//获取到第i条边的第2个顶点
			int p2 = getPosition(edges[i].end);

			//获取p1这个顶点在已有最小生成树中的终点
			int m = getEnd(ends, p1);
			//获取p2这个顶点在已有最小生成树中的终点
			int n = getEnd(ends, p2);
			//是否构成回路
			if(m != n) { //没有构成回路
				ends[m] = n; // 设置m 在"已有最小生成树"中的终点
				rets[index++] = edges[i]; //有一条边加入到rets数组
			}
		}
		//<E,F> <C,D> <D,E> <B,F> <E,G> <A,B>。
		//统计并打印 "最小生成树", 输出  rets
		System.out.println("最小生成树为");
		for(int i = 0; i < index; i++) {
			System.out.println(rets[i]);
			totalWeight += rets[i].weight;
		}
		System.out.println("最小生成树的权值为:" + totalWeight);
	}
	/**
	 * 功能:对边进行排序处理, 冒泡排序
	 * @param edges 边的集合
	 */
	private void sortEdges(EData[] edges) {
		for(int i = 0; i < edges.length - 1; i++) {
			for(int j = 0; j < edges.length - 1 - i; j++) {
				if(edges[j].weight > edges[j+1].weight) {//交换
					EData tmp = edges[j];
					edges[j] = edges[j+1];
					edges[j+1] = tmp;
				}
			}
		}
	}
	/**
	 *
	 * @param ch 顶点的值,比如'A','B'
	 * @return 返回ch顶点对应的下标,如果找不到,返回-1
	 */
	private int getPosition(char ch) {
		for(int i = 0; i < vertexs.length; i++) {
			if(vertexs[i] == ch) {//找到
				return i;
			}
		}
		//找不到,返回-1
		return -1;
	}
	/**
	 * 功能: 获取图中边,放到EData[] 数组中,后面我们需要遍历该数组
	 * 是通过matrix 邻接矩阵来获取
	 * EData[] 形式 [['A','B', 12], ['B','F',7], .....]
	 * @return
	 */
	private EData[] getEdges() {
		int index = 0;
		//创建edges数组保存图的边
		EData[] edges = new EData[edgeNum];
		for(int i = 0; i < vertexs.length; i++) {
			//本来j应该从i开始遍历,但是顶点自身的邻接矩阵的位置为0
			for(int j=i+1; j <vertexs.length; j++) {//把自身为0的情况也排除,所以j = i + 1开始
				if(matrix[i][j] != INF) {  //不是无穷大,说明i j 两个顶点之间有边
					//把边加入到edges数组中
					edges[index++] = new EData(vertexs[i], vertexs[j], matrix[i][j]);
				}
			}
		}
		return edges;
	}
	/**
	 * 功能: 获取下标为i的顶点的终点(), 用于后面判断两个顶点的终点是否相同
	 * @param ends : 数组就是记录了各个顶点对应的终点是哪个,ends 数组是在遍历过程中,逐步形成
	 * @param i : 表示传入的顶点对应的下标
	 * @return 返回的就是 下标为i的这个顶点对应的终点的下标, 一会回头还有来理解
	 */
	private int getEnd(int[] ends, int i) { // i = 4 [0,0,0,0,5,0,0,0,0,0,0,0]
		while(ends[i] != 0) {
			i = ends[i];
		}
		return i;
	}

}

//创建一个类EData ,它的对象实例就表示一条边
class EData {
	char start; //边的一个点
	char end; //边的另外一个点
	int weight; //边的权值
	//构造器
	public EData(char start, char end, int weight) {
		this.start = start;
		this.end = end;
		this.weight = weight;
	}
	//重写toString, 便于输出边信息
	@Override
	public String toString() {
		return "边 <" + start + ", " + end + "> 权值为= " + weight + "";
	}
}

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

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

相关文章

Java基于J2EE的流浪动物收容与领养管理系统

随着城市饲养宠物日益增加&#xff0c;流浪动物也越来越多&#xff0c;本文对流浪动物出现的原因&#xff0c;引发的社会问题以及流浪动物的保护等方面进行思考阐述,以期唤醒人们对动物福利的关注和对生命的珍爱。 通过以上的调研研究发现&#xff0c;如此多的流浪动物是如此的…

肠道菌群代谢组学之粪便微生物移植治疗原发性硬化性胆管炎

​ The American Journal of GASTROENTEROLOGY (IF10.241) 10位原发性硬化性胆管炎患者的粪便微生物移植&#xff1a;一个试点的临床试验 研究背景 百趣代谢组学分享&#xff0c;原发性硬化性胆管炎&#xff08;Primary sclerosing cholangitis&#xff0c;PSC&#xff09;是…

【Java语言】— 快速入门

Java背景知识 Java是美国sun公司在1995年推出的一门计算机高级编程语言。 Java早期称为Oak&#xff08;橡树&#xff09;&#xff0c;后改为Java。 Java之父:詹姆斯高斯林。 2009年sun公司被Oracle公司收购。 为什么用Java 世界上最流行的编程语言之一&#xff0c;在国内使用…

生成式AI结合3D、XR怎么玩?NVIDIA、Niantic等公司已入局

最近生成式AI风头有点大&#xff0c;这种技术只需要用文字就能作画&#xff0c;而且效果惊艳&#xff0c;堪比专业画师的作品。其中一些热门的方案包括DALL-E 2、Midjourney、BariumAI、D-ID AI、Stable Diffusion等等&#xff0c;这些工具简单、好玩&#xff0c;已经被无数网友…

BYD精制项目除铜工艺去除铜离子

某精细化工公司BYD精制项目 工艺选择 过滤系统螯合树脂除铜系统合格品回收箱 工艺原理 在不应该1,4丁炔二醇的情况下去除铜离子 项目背景 1,4-丁炔二醇BYD&#xff08;but-2-yne-1,4-diol&#xff09;是一种重要的中间体化工原料&#xff0c;广泛应用于生产丁二醇及其下游产…

2022CTF培训(五)字符串混淆进阶代码自解密

附件下载链接 复杂的字符串混淆 原理 之前的字符串混淆是一次性解密的&#xff0c;找到解密函数即可获得所有字符串&#xff0c;同时执行解密函数后内存中也可直接获得所有字符串。 因此对抗人员升级了混淆技术&#xff0c;使得解密仅在使用时发生&#xff0c;从而避免了全部…

微机原理不挂科

微机原理1.计算机基础1.1数制码值转换1.2码制1.3微机组成2.8088/8086微处理器2.1CPU内部结构2.2寄存器2.3存储器分段和地址空间2.4堆栈2.5 8086/8088CPU引脚2.6 时序与总线操作3.指令系统3.2寻址方式3.3语法规则3.4数据传送指令3.5算术运算指令3.6逻辑运算与移位指令3.7串操作指…

(二十) 共享模型之工具【JUC】【线程安全集合类】

一、线程安全集合类概述 线程安全集合类可以分为三大类&#xff1a;&#xff08;1&#xff09;遗留的线程安全集合如 Hashtable &#xff0c; Vector&#xff08;2&#xff09;使用 Collections 装饰的线程安全集合&#xff0c;如&#xff1a; 1️⃣Collections.synchronizedCo…

[附源码]计算机毕业设计JAVA游戏账号交易平台

[附源码]计算机毕业设计JAVA游戏账号交易平台 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybati…

Redis单机集群

先放张图 上图就是典型的哨兵模式 salve&#xff1a;从服务器&#xff0c;需要进行同步主服务器的数据 master&#xff1a;主服务器&#xff0c;负责执行客户端的请求&#xff0c;将数据更新信息发送给从服务器&#xff0c;保持数据一致 哨兵&#xff1a;接受客户端请求&…

【前端】前端监控体系

文章目录一、所需的数据1.1、生命周期数据1.2、HTTP测速数据1.3、系统异常数据1.4、用户行为数据1.5、用户日志二、埋点与收集2.1、数据埋点2.1、数据上报2.3、数据监控对于一个应用来说&#xff0c;除了前期的开发和设计&#xff0c;在项目上线后端维护很重要&#xff0c;其中…

Docker 讲解与基本操作

哈喽~大家好&#xff0c;这篇来看看Docker 讲解与基本操作。 &#x1f947;个人主页&#xff1a;个人主页​​​​​ &#x1f948; 系列专栏&#xff1a;【微服务】 &#x1f949;与这篇相关的文章&#xff1a; SpringCloud Sentinel 使用Spr…

最优化方法——QR分解

目录 系列文章目录 一、问题 二、实验思路综述 1.实验工具及算法 2.实验数据 3.实验目标 4.实验步骤 三、相关线性代数知识导入 1.线性无关与基 2.标准正交 3.Gram-Schmidt(正交化)算法 四、QR分解 1.Gram-Schmidt QR 1.1 算法原理 1.2 算法流程 1.3 复杂度分析…

JSP连接MySQL数据库

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;JAVA开发者…

Allegro如何设置走线禁布区操作指导

Allegro如何设置走线禁布区操作指导 Allegro可以任意设置走线的禁布区,以下图为例,需要在两个pin中间设置一个所有层都不能走线的禁布区域 具体操作如下 选择shape Add Rect命令 Option选择画在Route keepout-All层,type选择Static solid 鼠标移动到器件pad附近,右击会…

JavaScript -- Map对象及常用方法介绍

文章目录Map1 Map介绍2 创建一个Map3 常用方法介绍4 将Map转换为数组5 从数组构建Map6 遍历MapMap 1 Map介绍 Map用来存储键值对结构的数据**&#xff08;key-value&#xff09;**Object中存储的数据就可以认为是一种键值对结构Map和Object的主要区别&#xff1a; Object中的…

在vue3项目中使用新版高德地图

高德开发平台 : 高德开放平台 | 高德地图API (amap.com) 1. 首先你要注册好账号登录 2. 获取key和密钥 自2021年12月02日升级&#xff0c;升级之后所申请的 key 必须配备安全密钥 jscode 一起使用 NPM方式安装和使用(基础版): 按 NPM 方式安装使用 Loader : npm i amap/amap…

OS——进程并发控制(五大经典问题信号量机制描述)

目录 一、经典问题信号量机制描述 1、任意两个进程可以并发的条件&#xff08;Bernstein条件&#xff09; 2、临界区管理原则 3、信号量的P、V操作 &#xff08;1&#xff09;P、V操作 &#xff08;2&#xff09;P、V操作的原则 &#xff08;3&#xff09;用信号量解决进…

[开发浏览器实战]关于Firefox火狐浏览器的说明一二(国内版 国际版区别 账号切换 插件-恢复关闭的标签页 插件-tempermonkey油猴)

[开发浏览器实战]关于Firefox火狐浏览器的说明一二1.下载地址2.同步账号不一样国内版3.浏览器关于内容不同:国内版国际版![在这里插入图片描述](https://img-blog.csdnimg.cn/8ca563d2aa2d43b0b52b6cf50bbffa0c.png)灵活使用firefox推荐插件1.tempermonkey油猴2.恢复关闭的标签…

从零学习VINS-Mono/Fusion源代码(五):VIO初始化

本节分析VIO初始化部分 VINS-Mono/Fusion代码学习系列&#xff1a; 从零学习VINS-Mono/Fusion源代码&#xff08;一&#xff09;&#xff1a;主函数 从零学习VINS-Mono/Fusion源代码&#xff08;二&#xff09;&#xff1a;前端图像跟踪 从零学习VINS-Mono/Fusion源代码&#x…