java 数据结构并查集详解
作者:〖雪月清〗 发布时间:2023-01-22 03:52:24
一、概述
并查集:一种树型数据结构,用于解决一些不相交集合的合并及查询问题。例如:有n个村庄,查询2个村庄之间是否有连接的路,连接2个村庄
两大核心:
查找 (Find) : 查找元素所在的集合
合并 (Union) : 将两个元素所在集合合并为一个集合
二、实现
并查集有两种常见的实现思路
快查(Quick Find)
查找(Find)的时间复杂度:O(1)
合并(Union)的时间复杂度:O(n)
快并(Quick Union)
查找(Find)的时间复杂度:O(logn)可以优化至O(a(n))a(n)< 5
合并(Union)的时间复杂度:O(logn)可以优化至O(a(n))a(n)< 5
使用数组实现树型结构,数组下标为元素,数组存储的值为父节点的值
创建抽象类Union Find
public abstract class UnionFind {
int[] parents;
/**
* 初始化并查集
* @param capacity
*/
public UnionFind(int capacity){
if(capacity < 0) {
throw new IllegalArgumentException("capacity must be >=0");
}
//初始时每一个元素父节点(根结点)是自己
parents = new int[capacity];
for(int i = 0; i < parents.length;i++) {
parents[i] = i;
}
}
/**
* 检查v1 v2 是否属于同一个集合
*/
public boolean isSame(int v1,int v2) {
return find(v1) == find(v2);
}
/**
* 查找v所属的集合 (根节点)
*/
public abstract int find(int v);
/**
* 合并v1 v2 所属的集合
*/
public abstract void union(int v1, int v2);
// 范围检查
public void rangeCheck(int v) {
if(v<0 || v > parents.length)
throw new IllegalArgumentException("v is out of capacity");
}
}
2.1 Quick Find实现
以Quick Find实现的并查集,树的高度最高为2,每个节点的父节点就是根节点
public class UnionFind_QF extends UnionFind {
public UnionFind_QF(int capacity) {
super(capacity);
}
// 查
@Override
public int find(int v) {
rangeCheck(v);
return parents[v];
}
// 并 将v1所在集合并到v2所在集合上
@Override
public void union(int v1, int v2) {
// 查找v1 v2 的父(根)节点
int p1= find(v1);
int p2 = find(v2);
if(p1 == p2) return;
//将所有以v1的根节点为根节点的元素全部并到v2所在集合上 即父节点改为v2的父节点
for(int i = 0; i< parents.length; i++) {
if(parents[i] == p1) {
parents[i] = p2;
}
}
}
}
2.2 Quick Union实现
public class UnionFind_QU extends UnionFind {
public UnionFind_QU(int capacity) {
super(capacity);
}
//查某一个元素的根节点
@Override
public int find(int v) {
//检查下标是否越界
rangeCheck(v);
// 一直循环查找节点的根节点
while (v != parents[v]) {
v = parents[v];
}
return v;
}
//V1 并到 v2 中
@Override
public void union(int v1, int v2) {
int p1 = find(v1);
int p2 = find(v2);
if(p1 == p2) return;
//将v1 根节点 的 父节点 修改为 v2的根结点 完成合并
parents[p1] = p2;
}
}
三、优化
并查集常用快并来实现,但是快并有时会出现树不平衡的情况
有两种优化思路:rank优化,size优化
3.1基于size的优化
核心思想:元素少的树 嫁接到 元素多的树
public class UniondFind_QU_S extends UnionFind{
// 创建sizes 数组记录 以元素(下标)为根结点的元素(节点)个数
private int[] sizes;
public UniondFind_QU_S(int capacity) {
super(capacity);
sizes = new int[capacity];
//初始都为 1
for(int i = 0;i < sizes.length;i++) {
sizes[i] = 1;
}
}
@Override
public int find(int v) {
rangeCheck(v);
while (v != parents[v]) {
v = parents[v];
}
return v;
}
@Override
public void union(int v1, int v2) {
int p1 = find(v1);
int p2 = find(v2);
if(p1 == p2) return;
//如果以p1为根结点的元素个数 小于 以p2为根结点的元素个数 p1并到p2上,并且更新p2为根结点的元素个数
if(sizes[p1] < sizes[p2]) {
parents[p1] = p2;
sizes[p2] += sizes[p1];
// 反之 则p2 并到 p1 上,更新p1为根结点的元素个数
}else {
parents[p2] = p1;
sizes[p1] += sizes[p2];
}
}
}
基于size优化还有可能会导致树不平衡
3.2基于rank优化
核心思想:矮的树 嫁接到 高的树
public class UnionFind_QU_R extends UnionFind_QU {
// 创建rank数组 ranks[i] 代表以i为根节点的树的高度
private int[] ranks;
public UnionFind_QU_R(int capacity) {
super(capacity);
ranks = new int[capacity];
for(int i = 0;i < ranks.length;i++) {
ranks[i] = 1;
}
}
public void union(int v1, int v2) {
int p1 = find(v1);
int p2 = find(v2);
if(p1 == p2) return;
// p1 并到 p2 上 p2为根 树的高度不变
if(ranks[p1] < ranks[p2]) {
parents[p1] = p2;
// p2 并到 p1 上 p1为根 树的高度不变
} else if(ranks[p1] > ranks[p2]) {
parents[p2] = p1;
}else {
// 高度相同 p1 并到 p2上,p2为根 树的高度+1
parents[p1] = p2;
ranks[p2] += 1;
}
}
}
基于rank优化,随着Union次数的增多,树的高度依然会越来越高 导致find操作变慢
有三种思路可以继续优化 :路径压缩、路径分裂、路径减半
3.2.1路径压缩(Path Compression )
在find时使路径上的所有节点都指向根节点,从而降低树的高度
/**
* Quick Union -基于rank的优化 -路径压缩
*
*/
public class UnionFind_QU_R_PC extends UnionFind_QU_R {
public UnionFind_QU_R_PC(int capacity) {
super(capacity);
}
@Override
public int find(int v) {
rangeCheck(v);
if(parents[v] != v) {
//递归 使得从当前v 到根节点 之间的 所有节点的 父节点都改为根节点
parents[v] = find(parents[v]);
}
return parents[v];
}
}
虽然能降低树的高度,但是实现成本稍高
3.2.2路径分裂(Path Spliting)
使路径上的每个节点都指向其祖父节点
/**
* Quick Union -基于rank的优化 -路径分裂
*
*/
public class UnionFind_QU_R_PS extends UnionFind_QU_R {
public UnionFind_QU_R_PS(int capacity) {
super(capacity);
}
@Override
public int find(int v) {
rangeCheck(v);
while(v != parents[v]) {
int p = parents[v];
parents[v] = parents[parents[v]];
v = p;
}
return v;
}
}
3.2.3路径减半(Path Halving)
使路径上每隔一个节点就指向其祖父节点
/**
* Quick Union -基于rank的优化 -路径减半
*
*/
public class UnionFind_QU_R_PH extends UnionFind_QU_R {
public UnionFind_QU_R_PH(int capacity) {
super(capacity);
}
public int find(int v) {
rangeCheck(v);
while(v != parents[v]) {
parents[v] = parents[parents[v]];
v = parents[v];
}
return v;
}
}
使用Quick Union + 基于rank的优化 + 路径分裂 或 路径减半
可以保证每个操作的均摊时间复杂度为O(a(n)) , a(n) < 5
来源:https://blog.csdn.net/qq_52595134/article/details/122829762


猜你喜欢
- 有段时间没有写博客了,也在努力的从传统单机开发向分布式系统过度,所以再次做一些笔记,以方便日后查看。直接进入正题吧,今天记录spring-b
- 先唠叨几句啊,由于公司 * 已经搭好了我就不费那劲琢磨搭建 * 的事了,直接开撸上传lib。下图是我放组件库的地方,本来想一个module拉出一
- 一、FeignClient注解FeignClient注解被@Target(ElementType.TYPE)修饰,表示FeignClient
- Spring Boot是什么众所周知 Spring 应用需要进行大量的配置,各种 XML 配置和注解配置让人眼花缭乱,且极容易出错,因此 S
- * 的工作原理如图 * 是由每一个action请求(request)都包装在一系列的 * 的内部,通过redirectAction再一次
- static :静态常量,静态方法,静态代码块静态变量: 静态变量属于类的,使用类名来访问,非静态变量是属于对象的,"必须&quo
- 在练习Java的Scanner时,EditPlus如何读取从键盘输入的数呢?例如如下程序,编译通过,运行时却输入不了数据:package m
- 本文实现了一个有趣的小东西:使用自定义View绘图,一边画线,画出的线条渐渐变淡,直到消失。效果如下图所示:用属性动画或者渐变填充(Shad
- Input源码解读——从"Show tabs"开始本文基于Android T版本
- 对于Android View的测量,我们一句话总结为:
- 环境说明:Android Studio 2.0V7包版本:com.android.support:appcompat-v7:23.4.0co
- 概述本文介绍通过java程序向PDF文档添加图片,以及替换和删除PDF中已有的图片。另外,关于图片的操作还可参考设置PDF 图片背景、设置P
- 前言前面小空带同学们学了EditText控件,又用其实践做了个验证码功能,以为这就完了吗?然而并没有。Android在5.0以后引入了Mat
- 一、内存和寻址概述可编程设备包含微处理器和一定数量的临时存储空间。临时存储器被称为随机存取存储器(RAM)。RAM类似于宿舍里成排存物柜的存
- 0.介绍预览针对需要在IOS手机上接入原生微信支付场景,调用微信进行支付。如图:1.资料准备1.1 账号注册打开https://open.w
- 1.添加加载更多布局1_初始化和隐藏代码在RefreshListView构造方法中调用private void initFooterView
- static修饰符是java里面非常常用的一个东西,用法也非常多。然而,在kotlin里竟然没有这个东西!那该如何替代呢?本文就总结了下ja
- 简介关于IDEA的介绍,引用自百度百科:IDEA全称 IntelliJ IDEA,是java编程语言开发的集成环境。IntelliJ在业界被
- 计算两点之间的距离然后在控制台输出,这个题目还是挺简单的。下面我们来看看具体代码。package com.swift;import java
- 方式1:dependency 本地jar包<dependency> <groupId>com.jouyp