【数据结构】五、树:8.并查集

news2024/9/27 15:32:06

4.并查集Disjoint Set

文章目录

    • 4.并查集Disjoint Set
      • 4.1查
      • 4.2并
      • ❗4.3代码实现
      • 4.4对union优化
      • 4.5对Find的优化(压缩路径)
      • ❗4.6并查集C代码(优化后)
        • 按秩合并

集合。在集合中将各个元素划分为若干个 互不相交的子集。

  • 如何表示"集合"关系?

用互不相交的树,表示多个“集合”。这些树构成一个森林。


并查集(Disjoint Set)是逻辑结构集合的一种具体实现,只进行“并”和“查”两种基本操作。

4.1查

  • 如何“查”到一个元素到底属于哪一个集合?

从指定元素出发,一路向上,找到根节点。

  • 如何判断两个元素是否属于同一个集合?

分别查到两个元素的根,判断根节点是否相同即可。

4.2并

  • 如何把两个集合合并一个集合?

让一棵树成为另一棵树的子树即可。

❗4.3代码实现

因为只需要查找到根结点来判断是不是一个根节点(一个集合),所以使用双亲表示法来实现树的存储,表示一个集合。

Find :“查”操作:确定一个指定元索所属集合。

最坏时间复杂度O(n):n个结点全是上一个的孩子

n
A
B
C
D

所有我们的优化就是尽量不让树长高。

Union:“并”操作:将两个不想交的集合合并为一个。

时间复杂度O(1)

#include<stdio.h>
#include<stdlib.h>
#define SIZE 10
int UFsets[SIZE];	//集合元素数组

//初始化并查集,S相当于父结点,一开始都是一个节点,所以都是-1
void Initial(int S[]){
    for (int i=0; i<SIZE; i++)
        S[i]=-1;
}

//Find“查”操作,找x所属集合(返回x所属根结点)
int Find(int S[], int x){
    while(S[x] >= 0)	//循环寻找x的根
        x=S[x];		//寻找它的父结点
    return x;	//根的S[]小于0(就是等于-1表示树的根)
}

//Union“并”操作,将两个集合合并为一个(把一个树的根变成另一个树的根的孩子)
void Union(int S[], int x, int y){
    int rootx=Find(S,x);
    int rooty=Find(S,y);
    //要求x与y是不同的集合
    if(rootx!=rooty){
        S[rooty] = rootx;//将根Root2连接到另一根Root1下面
    }
}


//判断元素x和y是否属于同一集合
int IsSame(int S[],int x,int y){
    if (Find(S,x)==Find(S,y))
        return 1;
    else
        return 0;
}


int main(){
    int S[SIZE];
    Initial(S);
    printf("初始状态:");
    for(int i=0;i<SIZE;i++) printf("%d ",S[i]);
    printf("\n");

    Union(S,0,1);
    Union(S,1,2);
    Union(S,2,3);
    Union(S,3,4);
    Union(S,4,5);
    printf("1-5合并后的状态:");
    for(int i=0;i<SIZE;i++) printf("%d ",S[i]);
    printf("\n");

    Union(S,0,7);
    printf("7  合并后的状态:");
    for(int i=0;i<SIZE;i++) printf("%d ",S[i]);
    printf("\n");
    
    printf("%d\n",Find(S,5));
    printf("%d\n",Find(S,6));

    return 0;
}

初始状态:-1 -1 -1 -1 -1 -1 -1 -1 -1 -1

1-5合并后的状态:-1 0 0 0 0 0 -1 -1 -1 -1

7 合并后的状态:-1 0 0 0 0 0 -1 0 -1 -1

0
6

另一种写法:

假如有编号为1, 2, 3, …, n的n个元素,我们用一个数组fa[]来存储每个元素的父节点(因为每个元素有且只有一个父节点,所以这是可行的)。一开始,我们先将它们的父节点设为自己。

//或者写成:用递归的写法实现对代表元素的查询:一层一层访问父节点,直至根节点(根节点的标志就是父节点是本身)。要判断两个元素是否属于同一个集合,只需要看它们的根节点是否相同即可。
int find(int x){
    if(fa[x] == x)
        return x;
    else
        return find(fa[x]);
}

//合并操作也是很简单的,先找到两个集合的代表元素,然后将前者的父节点设为后者即可。当然也可以将后者的父节点设为前者,这里暂时不重要。本文末尾会给出一个更合理的比较方法。
void merge(int i, int j){
    fa[find(i)] = find(j);
}

这里的Find可以简写为一行:

int find(int x){
	return x == fa[x] ? x : (fa[x] = find(fa[x]));
}

注意赋值运算符=的优先级没有三元运算符?:高,这里要加括号。

4.4对union优化

我们的优化目标是尽量不让树的高度更高,提高查找效率。

在这里插入图片描述

//Union“并”操作,将两个集合合并为一个(把一个树的根变成另一个树的根的孩子)
void Union(int S[], int x, int y){
    int rootx=Find(S,x);
    int rooty=Find(S,y);
    //要求x与y是不同的集合
    if(rootx == rooty){
        return;
    }

    if(S[rootx] <= S[rooty]){ 	//x结点数更多(因为是负数)
        S[rootx] += S[rooty];	//累加结点总数
        S[rooty] = rootx; 		//小树y合并到大树x
    }
    else {
        S[rooty] += S[rootx];	//累加结点总数
        S[rootx] = rooty; 		//小树x合并到大树y
    }
}

该方法构造的树高不超过 ⌊ l o g 2 n ⌋ + 1 \lfloor log_2n \rfloor+1 log2n+1

使得 Find 的最坏时间复杂度变为 O ( l o g 2 n ) O(log_2n) O(log2n)

4.5对Find的优化(压缩路径)

压缩路径:Find 操作,先找到根节点,再将查找路径上所有结点都挂到根结点下。

每次Find操作,先找根,再“压缩路径”,可使树的高度不超过O(α(n))。

α(n)是一个增长很缓慢的函数,对于常见的n值,迪常α(n)≤4,因此优化后并查集的Find、Union操作时间开销都很低。

//Find“查”操作优化,先找到根节点,再进行“压缩路径”
// 将查找路径上所有结点都挂到根结点下
int Find(int S[], int x){
    int root = x;
    while(S[root] >= 0)
        root=S[root];   //循环找到根
//压缩路径
    while(x != root){	//将查找路径上所有结点都挂到根结点下
        int temp=S[x];	//temp指向x的父节点
        S[x]=root;	    //把x直接挂到根节点下
        x=temp;         //继续操作x的父结点,准备也挂在root节点下
	}
	return root;//返回根节点编号
}

❗4.6并查集C代码(优化后)

#include<stdio.h>
#include<stdlib.h>
#define SIZE 10
int UFsets[SIZE];	//集合元素数组

//初始化并查集,S相当于父结点,一开始都是一个节点,所以都是-1
void Initial(int S[]){
    for (int i=0; i<SIZE; i++)
        S[i]=-1;
}

//Find“查”操作优化,先找到根节点,再进行“压缩路径”
// 将查找路径上所有结点都挂到根结点下
int Find(int S[], int x){
    int root = x;
    while(S[root] >= 0)
        root=S[root];   //循环找到根
//压缩路径
    while(x != root){	//将查找路径上所有结点都挂到根结点下
        int temp=S[x];	//temp指向x的父节点
        S[x]=root;	    //把x直接挂到根节点下
        x=temp;         //继续操作x的父结点,准备也挂在root节点下
	}
	return root;//返回根节点编号
}

//Union“并”操作,将两个集合合并为一个(把一个树的根变成另一个树的根的孩子)
void Union(int S[], int x, int y){
    int rootx=Find(S,x);
    int rooty=Find(S,y);
    //要求x与y是不同的集合
    if(rootx == rooty){
        return;
    }

    if(S[rootx] <= S[rooty]){ 	//x结点数更多(因为是负数)
        S[rootx] += S[rooty];	//累加结点总数
        S[rooty] = rootx; 		//小树y合并到大树x
    }
    else {
        S[rooty] += S[rootx];	//累加结点总数
        S[rootx] = rooty; 		//小树x合并到大树y
    }
}

//判断元素x和y是否属于同一集合
int IsSame(int S[],int x,int y){
    if (Find(S,x)==Find(S,y))
        return 1;
    else
        return 0;
}


int main(){
    int S[SIZE];
    Initial(S);
    printf("初始状态:");
    for(int i=0;i<SIZE;i++) printf("%d ",S[i]);
    printf("\n");

    Union(S,0,1);
    printf("1  合并后的状态:");
    for(int i=0;i<SIZE;i++) printf("%d ",S[i]);
    printf("\n");

    Union(S,1,2);
    Union(S,2,3);
    Union(S,3,4);
    Union(S,4,5);
    printf("2-5合并后的状态:");
    for(int i=0;i<SIZE;i++) printf("%d ",S[i]);
    printf("\n");

    Union(S,0,7);
    printf("7  合并后的状态:");
    for(int i=0;i<SIZE;i++) printf("%d ",S[i]);
    printf("\n\n");

    return 0;
}

初始状态:-1 -1 -1 -1 -1 -1 -1 -1 -1 -1
1 合并后的状态:-2 0 -1 -1 -1 -1 -1 -1 -1 -1
2-5合并后的状态:-6 0 0 0 0 0 -1 -1 -1 -1
7 合并后的状态:-7 0 0 0 0 0 -1 0 -1 -1

按秩合并
#include<stdio.h>
#include<stdlib.h>

#define SIZE 10
const int maxn = 5005;
int Fa[maxn],Rank[maxn];

//初始化(按秩合并)
void init(int n){
	for (int i=0; i<n; i++){
		Fa[i] = i;
		Rank[i] = 1;
	}
}

int find(int x){
    return x == Fa[x]? x:(Fa[x] = find(Fa[x]));//路径压缩
}

//合并(按秩合并)
 void merge(int i, int j) {
    int x = find(i), y = find(j);
    if (Rank[x] < Rank[y]){     //小树合并到大树
        Fa[x] = y;
    }
    else{
        Fa[y] = x;
    }
    // 合并完更新rank秩
    if (Rank[x] == Rank[y] && x!=y){
        Rank[y]++;
    }
}


int main(){
    init(SIZE);
    printf("初始状态:");
    for(int i=0;i<SIZE;i++) printf("%d(%d) ",Fa[i],Rank[i]);
    printf("\n");

    merge(0,1);
    printf("1  合并后的状态:");
    for(int i=0;i<SIZE;i++) printf("%d(%d) ",Fa[i],Rank[i]);
    printf("\n");

    merge(1,2);
    merge(2,3);
    merge(3,4);
    merge(4,5);
    printf("2-5合并后的状态:");
    for(int i=0;i<SIZE;i++) printf("%d(%d) ",Fa[i],Rank[i]);
    printf("\n");

    merge(2,7);
    printf("7  合并后的状态:");
    for(int i=0;i<SIZE;i++) printf("%d(%d) ",Fa[i],Rank[i]);
    printf("\n\n");

    return 0;
}

初始状态:0(1) 1(1) 2(1) 3(1) 4(1) 5(1) 6(1) 7(1) 8(1) 9(1)
1 合并后的状态:0(1) 0(2) 2(1) 3(1) 4(1) 5(1) 6(1) 7(1) 8(1) 9(1)
2-5合并后的状态:0(1) 0(2) 0(2) 0(2) 0(2) 0(2) 6(1) 7(1) 8(1) 9(1)
7 合并后的状态:0(1) 0(2) 0(2) 0(2) 0(2) 0(2) 6(1) 0(2) 8(1) 9(1)

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

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

相关文章

uniapp video播放视频 悬浮在屏幕无法滑动

背景&#xff1a; 在uniapp中&#xff0c;需要使用<video></video>标签进行播放动态src的视频。 1.在开发的时候&#xff0c;运行到浏览器&#xff0c;vedio标签正常&#xff1b;(使用HbuildX运行&#xff0c;运行 -->运行到浏览器)。 2.但是在打包成原生App&am…

【数据结构】栈篇

文章目录 1. 栈2. 栈的实现2.1 准备工作2.2 栈的初始化2.3 入栈2.4 出栈2.5 判断栈是否为空2.6 取出栈顶元素2.7 获取栈中有效元素个数2.8 销毁栈效果图 3.代码整合 1. 栈 栈是一种特殊的线性表&#xff0c;其只允许固定一端进行插入和删除元素操作。进行数据的插入和删除操作的…

qt项目之在线考试系统----------MVC使用模型-视图-控制器

1、什么是MVC的设计模式 在Qt中,MVC是一种设计模式,全称为Model-View-Controller(模型-视图-控制器)。这是一个经典的设计模式,用于将数据表示(Model)、用户界面(View)和业务逻辑(Controller)分离。具体来说,MVC设计模式在Qt中的应用如下: Model(模型):表示应用…

C++之从C过渡(下)

C之从C过渡&#xff08;下&#xff09; 接着上一篇&#xff0c;从引用开始往下讲解。 引用的特性 引⽤在定义时必须初始化⼀个变量可以有多个引⽤引⽤⼀旦引⽤⼀个实体&#xff0c;再不能引⽤其他实体 C的引用不能完全替代指针。比如&#xff0c;在链表结点中我们会存储指向下…

2024下半年EI收录的老牌会议,检索超快!

在科研领域&#xff0c;EI作为全球公认的工程技术领域重要检索工具&#xff0c;其收录的会议论文往往代表着某一领域内的最新研究成果与前沿技术。对于广大科研工作者而言&#xff0c;能够在EI收录的老牌会议上发表论文&#xff0c;不仅是对自身研究能力的一种肯定&#xff0c;…

pinctrl子系统做功能的切换.

SD卡和debug口中sdmmc和uart共用同一组pin脚,需实现在sd使用的时候切换到sdmmc不插入sd卡的时候使用debug口功能。 sd卡有检测脚可以作为切换的标志所以我们的切换要在sd卡的驱动中去做。 第一步&#xff1a; 使能俩个功能的dts并去除不能切换的pinctrl&#xff0c;只有一个节点…

自动回复的AI小助手,人工智能还是人工智障

最近在运营公司的百家号账号。因为老杨和同事们在一些大会上有干货满满的演讲&#xff0c;我们将它剪辑成比较短的视频&#xff0c;放在一些平台上供大家观看。百家号因百度的关系&#xff0c;搜索的引流会好一些。 一开始每次发好视频&#xff0c;就会有播放量。几次之后&…

Java每日一题———删除有序数组中的重复项

这个问题可以通过使用双指针技术来解决。我们可以使用两个指针&#xff0c;一个慢指针 slowRunner 用于跟踪新数组的末尾&#xff0c;另一个快指针 fastRunner 用于遍历数组。每当 fastRunner 遇到一个新的唯一元素时&#xff0c;就将其复制到 slowRunner 指向的位置&#xff0…

创建谷歌外链的常见错误及避免方法!

创建谷歌外链是个技术活&#xff0c;很多人在这个过程中容易犯错。了解这些常见错误和如何避免它们可以帮助你更有效地提升你的SEO表现。 其一&#xff0c;忽视锚文本多样性。有些人在建立外链时&#xff0c;总是使用相同的锚文本&#xff0c;这看起来很不自然&#xff0c;可能…

基于python爬虫技术的bilibili网用户数据采集系统的设计与实现-计算机毕业设计源码55962

摘要 在当今信息爆炸的时代&#xff0c;互联网已经成为人们获取信息、交流思想的重要平台。作为国内领先的弹幕视频网站&#xff0c;Bilibili凭借其独特的弹幕文化和丰富的内容生态&#xff0c;吸引了亿万用户的关注。这些用户生成的海量数据蕴含着丰富的信息&#xff0c;对于理…

异常(Java)

目录 1. 异常的概念 2. 异常的分类 3. 异常的处理 4. 异常的抛出 5. 异常的捕获 5.1 异常声明throws 5.2 try-catch捕获并处理 5.3 finally 6. 异常的处理流程 7. 自定义异常类 1. 异常的概念 异常就是在程序执行过程中发生的不正常的行为.异常中断了正在执行程序的…

Cross-Modality Person Re-identification with Memory-Based Contrastive Embedding

文章目录 题目&#xff1a;Cross-Modality Person Re-identification with Memory-Based Contrastive Embedding&#xff08;基于记忆对比嵌入的跨模态人物再识别&#xff09;摘要论文分析网络框架1、Problem Definition&#xff08;模态预处理&#xff09;2、Learning Modalit…

RUM技术探索:前端监控数据采集与实践

​​随着互联网技术的不断演进&#xff0c;Web应用程序正日益呈现出复杂多变与高度动态性的特征。用户渴望获得快速的页面加载、流畅的交互体验以及高度的可靠性。为了满足这些&#xff0c;实时监控 Web 应用的性能和行为变得至关重要。前端监控让开发者能够深入了解应用的表现…

Hack The Box-Resource

总体思路 phar反序列化->SSH CA私钥泄露->SSH CA私钥滥用->SSH脚本滥用 信息收集&端口利用 nmap -sSVC itrc.ssg.htb目标开放了两个ssh端口和一个80端口&#xff0c;先查看80端口 网站是一个SSG IT资源中心&#xff0c;主要用于解决网站问题、管理 SSH 访问、清…

免费【2024】springboot 付费自习室管理系统的设计与实现

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

正点原子imx6ull-mini-Linux驱动之Linux 音频驱动实验

虽然mini板子没有这个资源&#xff0c;但是可以学学移植这个软件。 音频是我们最常用到的功能&#xff0c;音频也是 linux 和安卓的重点应用场合。I.MX6ULL 带有 SAI 接口&#xff0c;正点原子的 I.MX6ULL ALPHA 开发板通过此接口外接了一个 WM8960 音频 DAC 芯片&#xff0c;…

《程序猿入职必会(10) · SpringBoot3 整合 MyBatis-Plus》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

使用samba在ubuntu和windows之间共享文件

1、在ubuntu上安装samba 在终端输入命令 sudo apt update sudo apt install samba 2、配置samba 打开samba 的配置文件 sudo nano /etc/samba/smb.conf 在文件末尾添加以下内容 [shared] path /home/lzx available yes valid users lzx read only no browsable yes…

【Redis进阶】Redis的持久化RDB和AOF

目录 持久化 RDB持久化 概念 原理 RDB 持久化的详细工作流程 1触发持久化&#xff1a; 2创建子进程&#xff1a; 3数据写入 RDB 文件&#xff1a; 4替换旧文件&#xff1a; 5回收子进程&#xff1a; RDB持久化的触发方式 1.手动触发&#xff1a; 2.自动触发&#…