并查集<基于ranks 的优化,基于Path Spliting的优化>

news2024/7/4 6:07:57

需求分析

假设有n个村庄,有些村庄之间有连接的路,有些村庄之间并没有连接的路

在这里插入图片描述
请你设计一个数据结构,能够快速执行2个操作

  1. ◼ 查询2个村庄之间是否有连接的路
  2. ◼ 连接2个村庄

首先思考在现有的数据结构能否实现上面的功能,数组、链表、平衡二叉树、集合(Set)?貌似可以,但是查询、连接的时间复杂度都是:O(n),杀鸡用牛刀的感觉!

引出我们今天提出的数据结构并查集,并查集能够办到查询、连接的均摊时间复杂度都是 O( α n) ,α n < 5,可以认为是常数级别。并查集非常适合解决这类“连接”相关的问题。

并查集(Union Find)

详细介绍:https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Time_complexity
并查集也叫作不相交集合(Disjoint Set)
并查集有2个核心操作

  1. ◼ 查找(Find):查找元素所在的集合(这里的集合并不是特指Set这种数据结构,是指广义的数据集合)
  2. ◼ 合并(Union):将两个元素所在的集合合并为一个集合

◼ 并查集核心接口定义如下:

public interface Union<V>{
	void find(V v1);   //查找
	void union(V v1,V v2);    //合并
}

实现思路

  1. ◼ Quick Find 查找时间复杂度为O(1),合并时间复杂度O(n)
  2. ◼ Quick Union 查找(Find)的时间复杂度:O(logn),可以优化至 O(a(n)),a(n)<5;合并(Union)的时间复杂度:O(logn),可以优化至 O(a(n)),a(n)<5;

所以我们一般采用第二种Quick Union的思路。

如何存储数据?

假设并查集处理的数据都是整型,那么可以用整型数组来存储数据,一开始,默认一个村庄单独作为一个集合(自己指向自己),之后再通过union 的形式连接 【初始化时,每个元素各自属于一个单元素集合】
在这里插入图片描述
连接之后

在这里插入图片描述
因此,并查集是可以用数组实现的树形结构(二叉堆、优先级队列也是可以用数组实现的树形结构)


代码实现(quick union为例,基于ranks的优化【union】,基于路径分裂的优化【find】)

初始化
初始化时,每个元素各自属于一个单元素集合(自己指向自己)
在这里插入图片描述
在这里插入图片描述
Quick Union 的 union(v1, v2):让 v1 的根节点指向 v2 的根节点或者v2的根节点指向v1的根节点,这里就有优化的空间了,到底是谁嫁接到谁身上呢?我们的想法是树低的嫁接到树高的节点,这样不会让树的高度变高,搜索的效率可以提高。因此我们还需要维护一个树的高度的数组ranks。初始值高度均为1
因此上面的初始化代码改造为:
在这里插入图片描述
◼ union函数的实现

在这里插入图片描述
Quick Union 的 find(v1):找到 v1 的根节点,这里就有优化的空间了,在往上找的过程中,可以降低树的高度,采用路径分裂的思路,将路径上的每一个节点都指向其祖父节点,从而降低树的高度。当然有一种路径压缩的方案,就是将所有节点均指向根节点,这个成本有点高,我们折中考虑采用路径分裂。

◼ find函数的实现
在这里插入图片描述

完整代码如下

class Union{
    int[] parenrs;
    int[] ranks;

    public Union(int cap){
        parenrs = new int[cap];
        ranks = new int[cap];
 		for (int i = 0; i < parents.length; i++) {
			parents[i] = i;
		}
		Arrays.fill(ranks,1);
    }
    public int find(int v){
        while(v!=parenrs[v]){
            int p = parenrs[v];
            parenrs[v] = parenrs[p];
            v = p;
        }
        return v;
    }
    public void union(int a,int b){
        int ap = find(a);
        int bp = find(b);
        if(ap==bp){
            return;
        }
        if(ranks[ap]>ranks[bp]){
            parenrs[bp] = ap;
        }else if(ranks[ap]<ranks[bp]){
            parenrs[ap] = bp;
        }else{  
            parenrs[ap] = bp;
            ranks[bp]+=1;
        }

    }
    public boolean isSame(int a,int b){
        return find(a) == find(b);
    }
}

测试代码

	@Test
    public void testTime() {
		Unionu = new Union(100000);
		uf.union(0, 1);
		uf.union(0, 3);
		uf.union(0, 4);
		uf.union(2, 3);
		uf.union(2, 5);
		
		uf.union(6, 7);

		uf.union(8, 10);
		uf.union(9, 10);
		uf.union(9, 11);
		
		Asserts.test(!uf.isSame(2, 7));

		uf.union(4, 6);
		
		Asserts.test(uf.isSame(2, 7));
		
		Times.test(uf.getClass().getSimpleName(), () -> {
			for (int i = 0; i < count; i++) {
				uf.union((int)(Math.random() * count), 
						(int)(Math.random() * count));
			}
			
			for (int i = 0; i < count; i++) {
				uf.isSame((int)(Math.random() * count), 
						(int)(Math.random() * count));
			}
		});
	}

class Asserts {
	public static void test(boolean value) {
		try {
			if (!value) throw new Exception("测试未通过");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
class Times {
	private static final SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss.SSS");
	
	public interface Task {
		void execute();
	}
	
	public static void test(String title, Task task) {
		if (task == null) return;
		title = (title == null) ? "" : ("【" + title + "】");
		System.out.println(title);
		System.out.println("开始:" + fmt.format(new Date()));
		long begin = System.currentTimeMillis();
		task.execute();
		long end = System.currentTimeMillis();
		System.out.println("结束:" + fmt.format(new Date()));
		double delta = (end - begin) / 1000.0;
		System.out.println("耗时:" + delta + "秒");
		System.out.println("-------------------------------------");
	}
}

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

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

相关文章

JVM学习之运行时数据区

运行时数据区 概述 内存 内存是非常重要的系统资源&#xff0c;是硬盘和CPU的中间桥梁&#xff0c;承载着操作系统和应用程序的实时运行。JVM内存布局规定了Java在运行过程中内存申请&#xff0c;分配&#xff0c;管理的策略&#xff0c;保证了JVM高效稳定运行。不同的JVM对于…

HTTP 302错误:临时重定向

在Web开发中&#xff0c;HTTP状态码是用于表示Web服务器响应的各种状态。其中&#xff0c;HTTP 302错误表示临时重定向&#xff0c;这意味着请求的资源已被临时移动到其他位置&#xff0c;并且服务器已经提供了新的URL&#xff0c;以便客户端可以重新发送请求。 了解HTTP 302错…

[计网01] 物理层 详细解析笔记,特性

计算机网络的物理层是网络协议栈中的第一层&#xff0c;负责传输原始的比特流&#xff08;bitstream&#xff09;通过物理媒介进行通信。物理层主要关注传输介质、信号的编码和调制、数据传输速率以及数据传输的物理连接等方面。 相关特性 机械特性&#xff08;Mechanical Ch…

网络安全—学习溯源和日志分析

日志分析的步骤&#xff1a; 判断是否为攻击行为 不是&#xff1a;不用处理 是&#xff1a;判断攻击是否成功或者失败 攻击失败&#xff1a;判断IP地址是否为恶意地址&#xff0c;可以让防火墙过滤IP地址 攻击成功&#xff1a;做应急处置和溯源分析 应急处置&#xff1a;网络下…

[楚慧杯 2023] web

文章目录 eaaevalupload_shell eaaeval 打开题目&#xff0c;源码给了用户密码 登陆后啥也没有&#xff0c;扫一下发现源码泄露www.zip <?php class Flag{public $a;public $b;public function __construct(){$this->a admin;$this->b admin;}public function _…

Python计算圆的面积,几何学技法大解析!

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;我是彭涛&#xff0c;今天为大家分享 Python计算圆的面积&#xff0c;几何学技法大解析&#xff0c;全文3800字&#xff0c;阅读大约15分钟。 在本文中&#xff0c;将深入探讨如何使用 Python 计算圆的面积&…

用23种设计模式打造一个cocos creator的游戏框架----(十八)责任链模式

1、模式标准 模式名称&#xff1a;责任链模式 模式分类&#xff1a;行为型 模式意图&#xff1a;使多个对象都有机会处理请求&#xff0c;从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链&#xff0c;并沿着这条链传递该请求&#xff0c;直到有一个对象处…

『OPEN3D』1.5.1 动手实现点云暴力最近邻

本专栏地址: https://blog.csdn.net/qq_41366026/category_12186023.html?spm=1001.2014.3001.5482https://blog.csdn.net/qq_41366026/category_12186023.html?spm=1001.2014.3001.5482 1、暴力最近邻法 暴力最近邻法 (Brute-force Nearest Neighbour Search,BF 搜索) 是…

【数据结构】哈希表算法总结

知识概览&#xff08;哈希表&#xff09; 哈希表可以将一些值域较大的数映射到较小的空间内&#xff0c;通常用x mod 质数的方式进行映射。为什么用质数呢&#xff1f;这样的质数还要离2的整数幂尽量远。这可以从数学上证明&#xff0c;这样冲突最小。取余还是会出现冲突情况。…

ElasticSearch学习篇8_Lucene之数据存储(Stored Field、DocValue、BKD Tree)

前言 Lucene全文检索主要分为索引、搜索两个过程&#xff0c;对于索引过程就是将文档磁盘存储然后按照指定格式构建索引文件&#xff0c;其中涉及数据存储一些压缩、数据结构设计还是很巧妙的&#xff0c;下面主要记录学习过程中的StoredField、DocValue以及磁盘BKD Tree的一些…

【数据挖掘 | 相关性分析】Jaccard相似系数详解、关于集合的相关性(详细案例、附完详细代码实现和实操、学习资源)

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

《opencv实用探索·二十》点追踪技术

前言&#xff1a; 在学习点追踪技术前需要先了解下光流发追踪目标&#xff0c;可以看上一章内容&#xff1a;光流法检测运动目标 如果以光流的方式追踪目标&#xff0c;基本上我们可以通过goodFeaturesToTrack函数计算一系列特征点&#xff0c;然后通过Lucas-Kanade算法进行一…

Java智慧工地源码,智慧工地管理平台的技术架构和工作原理

智慧工地管理平台是将互联网的理念和技术引入建筑工地&#xff0c;从施工现场源头抓起&#xff0c;最大程度的收集人员、安全、环境、材料等关键业务数据&#xff0c;依托物联网、互联网&#xff0c;建立云端大数据管理平台&#xff0c;形成“端云大数据”的业务体系和新的管理…

考虑使用自定义的序列化形式

在Java中&#xff0c;有时候我们可能需要考虑使用自定义的序列化形式&#xff0c;以满足特定的需求或优化序列化过程。这通常涉及到实现Serializable接口的类&#xff0c;并自定义writeObject和readObject方法。以下是一个简单的例子&#xff0c;演示了如何使用自定义的序列化形…

货物数据处理pandas版

1求和 from openpyxl import load_workbook import pandas as pddef print_hi(name):# Use a breakpoint in the code line below to debug your script.print(fHi, {name}) # Press CtrlF8 to toggle the breakpoint.# Press the green button in the gutter to run the scr…

vue中2种取值的方式

1.url是这种方式的&#xff1a;http://localhost:3000/user/1 取得参数的方式为&#xff1a;this.$route.params.id 2.url为get方式用&#xff1f;拼接参数的&#xff1a;http://localhost:3000/user?phone131121123&companyId2ahttp://localhost:3000/ 取得参数值的方式…

HTTP代理神器Fiddler的配置

HTTP代理神器Fiddler Fiddler的简介 Fiddler是位于客户端和服务器端之间的代理&#xff0c;也是目前最常用的抓包工具之一 。它能够记录客户端和服务器之间的所有 请求&#xff0c;可以针对特定的请求&#xff0c;分析请求数据、设置断点、调试web应用、修改请求的数据&#…

不是生活有意思,是你热爱生活它才有意思

明制汉服的设计 同样是一款很重工的外套 细节上也是做到了极致 顺毛毛呢面料 领口袖口拼接仿貂毛环保毛条 前胸欧根纱刺绣圆形布 袖子贴民族风珠片刺绣织带 门襟搭配金属子母扣&#xff0c;真盘扣设计 时尚经典&#xff0c;搭配马面裙孩子穿上 真的很有气质奢华富贵 …

Android hwcomposer服务启动流程

Android hwcomposer服务启动流程 客户端 binder远程调用 服务端 surfaceflinger --binder--> hwcomposer .hal文件编译时生成支持binder进程间远程调用通信的cpp文件 在out/soong/.intermediates/hardware/interfaces/graphics/composer/2.1/ 目录下找…

时序预测 | Python实现GRU电力需求预测

时序预测 | Python实现GRU电力需求预测 目录 时序预测 | Python实现GRU电力需求预测预测效果基本描述程序设计参考资料预测效果 基本描述 该数据集因其每小时的用电量数据以及 TSO 对消耗和定价的相应预测而值得注意,从而可以将预期预测与当前最先进的行业预测进行比较。使用该…