【并查集(Disjointed Set)简介】

前言

并查集(Disjointed Set)是一种非常精巧而实用的数据结构,用于处理不相交集合的合并、查找
经典应用:

  • 连通性判断
  • 最小生成树Kruskal算法
  • 最近公共祖先

应用背景1 “帮派”

  • 有n个人,他们属于不同的帮派;
  • 如果1号、2号是朋友,1、3号也是朋友,那么他们属于一个帮派;
  • 问有多少个帮派,每人属于哪个帮派?
  • 用并查集能简洁的表示这个关系。
    帮派示意图

应用背景2 “饭桌”

  • 有n个人在一起吃法,有些人互相认识
  • 认识的人想坐在一起而不想跟陌生人坐
  • 例如:A认识B,B认识C,那么A、B、C会坐到一张桌子上
  • 给出认识的人,问需要多少张桌子?
    桌子示意图

并查集的基本操作

初始化

定义int s[ ]是以结点i为元素的并查集
初始化:令s[i] = i
并查集初始化

初始的时候每个人都是一个独立的帮派

//初始化
void init_set(){
for(int i = 1;i<N;i++)
	s[i] = i;
}

合并

例1:加入第一个朋友关系(1,2)

  • 在并查集s中,把结点1合并到结点二,也就是把结点1的集1改为结点2的集2
    合并(1,2)

例2:加入第二个朋友关系(1,3)

  • 查找结点1的集,是2,递归查找元素2的集是2
  • 把元素2的集2合并到结点3的集3.此时结点1、2、3都是属于一个集。
    合并(1,3)

例3:加入第三个朋友关系(2,4)

  • 同上
    -在这里插入图片描述
//合并
void union_set(int x,int y){
	x = find_set(x);
	y = find_set(y);
	if(x!=y)s[x] = s[y];
}

查找

  • 查找元素的集:是一个递归,直到元素的值和它的集相等,就找到了根结点的集
    示意图
//查找(递归)
int find_set(int x){
	return x == s[x]?x:find_set(s[x]);	
}

有多少个集?
如果s[i] = i ,这是一个根结点,是它所在的集的代表(帮主)

  • 统计根结点的数量,就是集的数量。
    有多少个帮主
    基本代码的复杂度
  • 查找find_set()、合并union_set()的搜索深度是树的长度,复杂度为O(n).
    在这里插入图片描述

优化

合并的优化(不常用)

  • 合并x和y时,先搜到他们的根结点
  • 合并这两个根结点:把一个根结点的集改成另一个根结点
  • 这两个根结点高度不同时,把高度较小的集合并到较大的集上,能减少数的高度。
    在这里插入图片描述

查询的优化:路径压缩

  • 查询find_set():沿着搜索路径找到根结点,这条路径可能很长。
  • 优化:沿路径返回时顺便把所属的集改成根结点
  • 下次再搜,复杂度为O(1)
    路径压缩
int find_set(int x){//没有路径压缩
	return x ==s[x]?x:find_set(s[x]);
}
int find_set(int x){有路径压缩
	if(x!=s[x]) s[x] = find_set(s[x]);
	return s[x];
}

例题

蓝桥幼儿园

  • 题目描述:蓝桥幼儿园的学生天真无邪,对她们来说,朋友的朋友就是自己的朋友。
  • 小明是蓝桥幼儿园的老师,这天他决定为学生举办一个交友活动,活动规则:用红绳连接两名学生,被连接的两个学生将成为朋友。
  • 请你帮忙写程序判断两个学生是否为朋友(默认自己和自己也是朋友)
  • 小朋友数:n<800000
#include<iostream>

#define N 800005
int s[N];
using namespace std;

void init_set(int n) {
	for (int i = 0; i < n; i++)
		s[i] = i;
}
int find_set(int x) {
	if (x != s[x]) s[x] = find_set(s[x]);
	return s[x];
}
void merge_set(int x, int y) {
	x = find_set(x);
	y = find_set(y);
	if (x != y) s[x] = s[y];
}
int main() {
	int n, m;
	cin >> n >> m;
	init_set(n);
	int x, y;
	for (int i = 0; i < m; i++) {
		cin >> x >> y;
		merge_set(x, y);
	}
	cin >> x >> y;
	if (find_set(x) == find_set(y)) cout << "YES";
	return 0;
}

合根植物

  • 题目描述:一种植物园,被分成m*n个小格子(东西方向m行,南北方向n行,每个格子里种了一株和根植物)
    植物的根可能会沿着南北或东西方向伸展,从而与另一个格子的植物和为一体
  • 如果我告诉你那些小格出现了连根现象,你能说出共有多少株合根植物吗?

并查集的简单应用:判断连通性


并查集的方法:

  • 1):初始化:
  • 把5个数初始化为5个集合,开始时他们属于不同的集合,说明这5个数字不连通
  • (2):连通(合并)
  • 如果两个数字相连,把这两个数字放到一个集合中;
  • 如果在一个集合中他们就连通。
  • (3):连通查询
  • 查询5个数是否是属于一个集合,如果是就是连通的。

  • 题目:有12张连在一起的12生肖的邮票从中剪下5张,要求必须是连着的,(仅连一个角不算相连)
  • 比如图中,粉红色所示的部分就是合格的剪取
    示意图
#include<iostream>
using namespace std;
int star[] = { 1,2,3,4,5,6,7,8,9,10.11,12 };
int num = 0;
int b[] = { -1,1,-5,5 };
bool check() {//检查star[0]~star[4]这5个数是否连通
	int set[5] = {0,1,2,3,4 };//用并查集判断5个数是否连通,初始值有5个集合,编号0~4
	for (int i = 0; i <= 4; i++) {//第i个数和第j个数是否相邻
		for (int j = 0; j <= 4; j++) {
			for (int k = 0; k <= 3; k++) {//k是上下左右四个方向
				if (star[i] + b[k] == star[j]) {
					int temp = set[j];
					set[j] = set[i];
					for (int s = 0; s <= 4; s++) {
						if (set[s] == temp)
							set[s] = set[i];
					}
			}
			}
		}
	}
	for (int i = 1; i <= 4; i++) {
		if (set[0] != set[i])return false;
		return true;
	}
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jie3606

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值