最容易理解的并查集详解

news2024/11/27 4:24:34

并查集

并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要查找一个元素在哪个集合中。 比如下面这幅图,总共有 10 个节点,他们互不相连,分别用 0~9 标记:

在这里插入图片描述
现在我们的 Union-Find 算法主要需要实现这两个 API:

class UF {
    /* 将 p 和 q 连接 */
    public void union(int p, int q);
    /* 判断 p 和 q 是否连通 */
    public boolean connected(int p, int q);
    /* 返回图中有多少个连通分量 */
    public int count();
}

比如上面这幅图,0~9 任意两个不同的点都不连通,调用connected都会返回 false,连通分量为 10 个。 如果现在调用union(0, 1),那么 0 和 1 被连通,连通分量降为 9 个。 再调用union(1, 2),这时 0,1,2 都被连通,调用connected(0, 2)也会返回 true,连通分量变为 8 个。

在这里插入图片描述
一开始的时候没有相互连通,就是这样:

class UF {
    // 记录连通分量
    private int count;
    // 节点 x 的节点是 parent[x]
    private int[] parent;

    /* 构造函数,n 为图的节点总数 */
    public UF(int n) {
        // 一开始互不连通
        this.count = n;
        // 父节点指针初始指向自己
        parent = new int[n];
        for (int i = 0; i < n; i++)
            parent[i] = i;
    }

    /* 其他函数 */
}

如果要连通两个点,那么则让其中的(任意)一个节点的根节点接到另一个节点的根节点上:

public void union(int p, int q) {
    int rootP = find(p);
    int rootQ = find(q);
    if (rootP == rootQ)
        return;
    // 将两棵树合并为一棵
    parent[rootP] = rootQ;
    // parent[rootQ] = rootP 也一样
    count--; // 两个分量合二为一
}

/* 返回某个节点 x 的根节点 */
private int find(int x) {
    // 根节点的 parent[x] == x
    while (parent[x] != x)
        x = parent[x];
    return x;
}

/* 返回当前的连通分量个数 */
public int count() { 
    return count;
}

这样,如果节点p和q连通的话,它们一定拥有相同的根节点:
public boolean connected(int p, int q) {
    int rootP = find(p);
    int rootQ = find(q);
    return rootP == rootQ;
}

并查集的完整实现:

public class UnionFind {
     private int count; //记录连通分量
     private int[]parent; //节点x的根节点是parent[x]
     public UnionFind(int n){
         //一开始互不相通
         this.count=n;
         //一开始,每个节点是自己的父节点
         parent=new int[n];
         for (int i = 0; i <n ; i++) {
             parent[i]=i;
         }
     }
      /*
      将p和q连接, 如果两个节点被连通,那么则让其中的一个根节点连接到另一个节点的根节点上
      */
     public void union(int p,int q){
         int rootP=find(p);
         int rootQ=find(q);
         if(rootP==rootQ){
             return;
         }
         //将两颗树合并为一颗
         parent[rootP]=rootQ; //parent[rootQ]=rootP 效果是一样的
         count--; //两个分量合二为一
     }
     //返回某个节点x的根节点
     private int find(int x){
          //根节点的parent[x]==x
          while (parent[x]!=x){
              x=parent[x];
          }
          return x;
     }
     /*
      判断p和q是否连通:如果两个节点是连通的,那么他们一定拥有相同的根节点
      */
     public boolean connected(int p,int q){
         int rootP=find(p);
         int rootQ=find(q);
         return rootP==rootQ;
     }
     /*
     返回具体有多少个连通分量
      */
      public int count(){
         return count;
      }
}

并查集练习1 冗余连接

树可以看成是一个连通且 无环 的 无向 图。

给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。

请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的边。
在这里插入图片描述
输入: edges = [[1,2], [1,3], [2,3]]
输出: [2,3]

在这里插入图片描述
输入: edges = [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]

解题思路:
树是一个连通且无环的无向图,在树中多了一条附加的边之后就会出现环,因此附加的边即为导致环出现的边。

可以通过并查集寻找附加的边。初始时,每个节点都属于不同的连通分量。遍历每一条边,判断这条边连接的两个顶点是否属于相同的连通分量。

  • 如果两个顶点属于不同的连通分量,则说明在遍历到当前的边之前,这两个顶点之间不连通,因此当前的边不会导致环出现,合并这两个顶点的连通分量。

  • 如果两个顶点属于相同的连通分量,则说明在遍历到当前的边之前,这两个顶点之间已经连通,因此当前的边导致环出现,为附加的边,将当前的边作为答案返回。

class Solution {
    public static int[] findRedundantConnection(int[][] edges) {
        int len=edges.length;
        UnionFind u=new UnionFind(len+1);
        for (int i=0;i<len;i++){
            if (u.isConnected(edges[i][0],edges[i][1])){
                return new int[]{edges[i][0],edges[i][1]};
            }else {
                u.union(edges[i][0],edges[i][1]);
            }
        }
        return new int[]{0};
    }
}

class UnionFind{
    private int count;
    private int[] parent;

    public UnionFind(int count){
        this.count=count;
        parent=new int[count];
        for (int i=0;i<count;i++){
            parent[i]=i;
        }
    }

    public void union(int a,int b){
        int parentA=find(a);
        int parentB=find(b);
        if (parentA==parentB) return;
        parent[parentA]=parentB;
        count--;
    }

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

    public int find(int x){
        if (x==parent[x]) {
            return x;
        }else {
            parent[x]=find(parent[x]);
            return parent[x];
        }
    }

    public int getCount(){
        return this.count;
    }
}

并查集练习2 省份数量

有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。

省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。

给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。

返回矩阵中 省份 的数量。
在这里插入图片描述在这里插入图片描述

解题思路

计算连通分量数的另一个方法是使用并查集。初始时,每个城市都属于不同的连通分量。遍历矩阵 isConnected,如果两个城市之间有相连关系,则它们属于同一个连通分量,对它们进行合并。

遍历矩阵 isConnected 的全部元素之后,计算连通分量的总数,即为省份的总数。

写法一:

class Solution {
    public int findCircleNum(int[][] isConnected) {
        int len= isConnected.length;
        UnionFind u=new UnionFind(len+1);
        for (int i=0;i<len;i++){
            for (int j=0;j<len;j++){
                if (isConnected[i][j]==1) u.union(i+1,j+1);
            }
        }
        return u.getCount()-1;
    }
}

class UnionFind{
    private int count;
    private int[] parent;

    public UnionFind(int count){
        this.count=count;
        parent=new int[count];
        for (int i=0;i<count;i++){
            parent[i]=i;
        }
    }

    public void union(int a,int b){
        int parentA=find(a);
        int parentB=find(b);
        if (parentA==parentB) return;
        parent[parentA]=parentB;
        count--;
    }

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

    public int find(int x){
        if (x==parent[x]) {
            return x;
        }else {
            parent[x]=find(parent[x]);
            return parent[x];
        }
    }

    public int getCount(){
        return this.count;
    }
}

写法2

class Solution {
    int[] f;
	
	public int findCircleNum(int[][] isConnected) {
		int n=isConnected.length;
		f=new int[n];
		for(int i=0;i<n;i++) {
			f[i]=i;
		}
		for(int i=0;i<isConnected.length;i++) {
			for(int j=0;j<isConnected.length;j++) {
				if(isConnected[i][j]==1) {
					int fi=find(i);
					int fj=find(j);
					if(fi==fj) {
						continue;
					}
					f[fi]=fj;
				}
			}
		}
		int ans=0;
		for(int i=0;i<n;i++) {
			if(f[i]==i) {
				ans++;
			}
		}
		return ans;
	}
	
	public int find(int x) {
		while(f[x]!=x) {
			x=f[x];
		}
		return x;
	}
}

并查集练习3 相似的字符串

如果交换字符串 X 中的两个不同位置的字母,使得它和字符串 Y 相等,那么称 X 和 Y 两个字符串相似。如果这两个字符串本身是相等的,那它们也是相似的。

例如,“tars” 和 “rats” 是相似的 (交换 0 与 2 的位置); “rats” 和 “arts” 也是相似的,但是 “star” 不与 “tars”,“rats”,或 “arts” 相似。

总之,它们通过相似性形成了两个关联组:{“tars”, “rats”, “arts”} 和 {“star”}。注意,“tars” 和 “arts” 是在同一组中,即使它们并不相似。形式上,对每个组而言,要确定一个单词在组中,只需要这个词和该组中至少一个单词相似。

给定一个字符串列表 strs。列表中的每个字符串都是 strs 中其它所有字符串的一个 字母异位词 。请问 strs 中有多少个相似字符串组?

字母异位词(anagram),一种把某个字符串的字母的位置(顺序)加以改换所形成的新词。

示例 1:
输入:strs = [“tars”,“rats”,“arts”,“star”]
输出:2

示例 2:
输入:strs = [“omv”,“ovm”]
输出:1

解题思路:
我们把每一个字符串看作点,字符串之间是否相似看作边,那么可以发现本题询问的是给定的图中有多少连通分量。于是可以想到使用并查集维护节点间的连通性。

我们枚举给定序列中的任意一对字符串,检查其是否具有相似性,如果相似,那么我们就将这对字符串相连。

在实际代码中,我们可以首先判断当前这对字符串是否已经连通,如果没有连通,我们再检查它们是否具有相似性,可以优化一定的时间复杂度的常数。

class Solution {
    int[] f;
    public int numSimilarGroups(String[] strs) {
        int n=strs.length;
        int m=strs[0].length();
        f=new int[n];
        for (int i = 0; i < n; i++) {
            f[i]=i;
        }
        for (int i=0;i<n-1;i++){
            for (int j=i+1;j<n;j++){
                int fi=find(i);
                int fj=find(j);
                if (fi==fj) continue;
                if (check(strs[i],strs[j],m)) f[fi]=fj;
            }
        }
        int ans=0;
        for (int i = 0; i < n; i++) {
            if (f[i]==i) ans++;
        }
        return ans;
    }

    public int find(int x){
        if (x==f[x]){
            return x;
        }else {
            f[x]=find(f[x]);
            return f[x];
        }
    }

    public boolean check(String str1,String str2,int m){
        int ans=0;
        for (int i = 0; i < str1.length(); i++) {
            if (str1.charAt(i)!=str2.charAt(i)){
                ans++;
            }
            if (ans>2){
                return false;
            }
        }
        return true;
    }
}

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

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

相关文章

MySQL之存储过程

MySQL存储过程1、基本介绍1.1、介绍存储过程&#xff1a;1.2、特点1.3、基本语法1.3.1、delimiter1.3.1、创建存储过程1.3.2、调用存储过程1.3.3、查看存储过程1.3.4、删除存储过程2、变量2.1、系统变量2.1.1、查询(会话、全局、模糊、精确)2.1.2、设置系统变量2.2、用户定义变…

IB学生必须具备的三大特质

以往的专栏亦提及过&#xff0c;修读IB课程要面对几大挑战。而要应对这些挑战&#xff0c;IB学生须具备以下三大条件&#xff1a; 时间管理能力 IBDP 首先&#xff0c;要对时间分配掌握得很好。两年的IB预科课程非常紧凑&#xff0c;不但每科都有其内部评核&#xff08;Interna…

VMware17虚拟机安装Ubuntu最新版本(Ubuntu22.04LTS)详细步骤

目录 一、概述 二、下载Ubuntu 22.04.1 LTS 三、在VMware虚拟机下安装Ubuntu22.04 四、配置网络 一、概述 Ubuntu是基于Linux内核开发的&#xff0c;免费下载&#xff0c;使用和分享的开源系统。如果需要在Linux下开发程序&#xff0c;这是一个很好的选择。本文介绍了Ubuntu最…

【问题解决】Tomcat启动服务时提示Filter初始化或销毁出现java.lang.AbstractMethodError错误

问题背景 最近在开发项目接口&#xff0c;基于SpringBoot 2.6.8&#xff0c;最终部署到外置Tomcat 8.5.85 下&#xff0c;开发过程中写了一个CookieFilter&#xff0c;实现javax.servlet.Filter接口&#xff0c;代码编译期正常。部署到外置Tomcat 8.5.85 下&#xff0c;在控制…

【Java寒假打卡】Java基础-类加载器

【Java寒假打卡】Java基础-类加载器概述类加载时机类加载的过程-加载类加载的过程-链接类加载的过程-初始化类加载器的分类类加载器-双亲委派模型类加载器-常用方法概述 负责将字节码文件加载到内存中 类加载时机 创建类的实例对象调用类的类方法访问类或者接口的类变量&am…

SymPy符号运算库与latex数学公式

SymPy符号运算库与latex数学公式sympylatexsympy SymPy是一个用于以符号运算为主的符号数学的Python库。它的目标是成为一个全功能的计算机代数系统(CAS)&#xff0c;同时保持代码尽可能的简单&#xff0c;以便易于理解和易于扩展。SymPy完全是用Python编写的。 官网地址:http…

【linux kernel】Linux设备驱动模型 | bus

文章目录一、导读二、与总线相关的数据结构&#xff08;2-1&#xff09;struct bus_type&#xff08;2-2&#xff09;struct subsys_private三、总线的初始化四、总线的操作接口&#xff08;4-1&#xff09;总线的注册&#xff08;4-2&#xff09;总线的注销&#xff08;4-3&am…

Linux的基本使用在Linux上部署程序

linux概述 Linux严格意义来说只是一个"操作系统内核"&#xff0c;一个完整的操作系统 操作系统内核 配套的应用程序 由于 Linux 是一个完全开源免费的内核&#xff0c;因此有些公司/开源组织又基于 Linux 内核&#xff0c;提供了不同的配套程序&#xff0c;这就构…

GAN“家族”又添新成员——EditGAN,不但能自己修图,还修得比你我都好

导语&#xff1a;从风格迁移到特征解耦、语言概念解耦&#xff0c;研究人员正通过数学和语言逐步改善GAN的功能。作者 | 莓酊编辑 | 青暮首先想让大家猜一猜&#xff0c;这四张图中你觉得哪张是P过的&#xff1f;小编先留个悬念不公布答案&#xff0c;请继续往下看。生成对抗网…

【蓝桥杯】历届真题 时间显示(省赛)Java

【问题描述】 小蓝要和朋友合作开发一个时间显示的网站。在服务器上&#xff0c;朋友已经获取了当前的时间&#xff0c;用一个整数表示&#xff0c;值为从1970年1月1日O0:00:00到当前时刻经过的毫秒数。 现在&#xff0c;小蓝要在客户端显示出这个时间。小蓝不用显示出年月日&a…

Allegro如何灌铜操作指导

Allegro如何灌铜操作指导 在做PCB设计平面层的铜皮时候,会需要用到灌铜的操作,如下图 灌铜可以让铜皮自动沿着Antietch画指定网络的铜皮 具体操作如下 点击Add Line命令选择Anti Etch的层面,比如Anti Etch画在L2层,线宽设置为40mil

TCP通信的三次握手和四次挥手详解

TCP通信的三次握手和四次挥手详解 计算机网络参考模型: 应用层:例如Modbus、Http、FTP 传输层:TCP、UDP 网络层:IP 数据链路层:MAC 物理层:RS485、RS232、以太网 TCP的包头: TCP包头为至少20字节 TCP包头解释  源端口号、目的端口号,用于建立连接时,确认源端口(本机…

2.Spring 等框架简单入门了解

1.Spring 1.什么是spring? 一个轻量级Java开发框架,目的是为了解决企业级应用开发 的业务逻辑层和其他各层的耦合问题. 两个核心特性&#xff0c;也就是依赖注入(dependency injection&#xff0c;DI)和面向切面编程(aspect- oriented programming&#xff0c;AOP) 2.IOC(控制…

一文带你秒懂十大排序

目录 一、排序的概述 二、插入排序 1、直接插入排序 2、希尔排序 二、选择排序 1、直接选择排序 2、堆排序 三、交换排序 1、冒泡排序 2、快速排序 四、归并排序 五、计数排序 六、基数排序 七、桶排序 八、排序总结 一、排序的概述 排序就是将一组…

pod私有库

私有库制作步骤 1、在gitlab上创建一个空项目&#xff0c;并用source tree导到本地&#xff0c;便于后面代码更新上传 2、cd 到项目下 执行pod lib create 【组件名】如&#xff1a;pod lib create TDAlertView 输入命令后会显示下载模板&#xff0c;会有几秒钟等待 Cloni…

一文搞懂 python 中的 classmethod、staticmethod和普通的实例方法的使用场景

什么是类方法&#xff08;classmethod&#xff09;/静态方法&#xff08;staticmethod&#xff09;和普通成员方法&#xff1f; 首先看这样一个例子&#xff1a; class A(object):def m1(self, n):# 属于实例对象&#xff0c;self 指代实例对象&#xff0c;print("self:…

Allegro如何更改钻孔孔符以及大小操作指导

Allegro如何更改钻孔孔符以及大小操作指导 PCB设计完成时,需要放出整板的钻孔表来,有的钻孔孔符以及大小并不是需要的,Allegro支持更改钻孔符以及大小,如下图 需要更改孔符以及大小, 具体操作如下 选择Manufacture选择NC

aws parallelcluster 理解 parallelcluster 集群的配置和使用

参考资料 Setup AWS ParallelCluster 3.0 with AWS Cloud9 200 HPC For Public Sector Customers 200 HPC pcluster workshop 200 Running CFD on AWS ParallelCluster at scale 400 Tutorial on how to run CFD on AWS ParallelCluster 400 Running CFD on AWS ParallelC…

CSS 伪元素也可以被用于反爬案例?来学习一下。26

先说一下什么是 CSS 中的伪元素&#xff0c;CSS 伪元素的概念是指在 CSS 中使用的一些特殊的元素&#xff0c;它们不存在于 HTML 文档中&#xff0c;而是由浏览器生成的元素&#xff0c;用于提供额外的样式控制。这些伪元素在 HTML 代码中不存在&#xff0c;但可以在 CSS 中通过…

[idekCTF 2023] Malbolge I Gluttony,Typop,Cleithrophobia,Megalophobia

这些题名字我都不认识&#xff0c;这是什么语呀。这个比赛感觉太难了&#xff0c;加上春节将近比较忙&#xff0c;仅作了4个简单题。记录一下。Misc/Malbolge I Gluttony这是个虚拟机的题&#xff0c;放入misc感觉有点不可思忆&#xff0c;题目给了7个命令&#xff0c;有"…