并查集**

 


 
 

  

并查集

  并查集是一种可以动态维护若干个不重叠的集合,并支持合并与查询的数据结构。并查集包括如下两个基本操作:

  1. Get,查询一个元素属于哪一个集合。
  2. Merge,把两个集合合并成一个大集合。

 
 

并查集的操作

  1. 并查集的存储
    使用一个数组 fa 保存父节点(根的父节点设为自己)。
int fa[SIZE];
  1. 并查集的初始化
    设有 n 个元素,起初所有元素各自构成一个独立的集合,即有 n 棵 1 个点的树。
for(int i = 1; i <= n; i++)
	fa[i] = i;
  1. 并查集的 Get 操作
    若 x 是树根,则 x 就是集合代表,否则递归访问 fa[x] 直至根节点。
int get(int x){
	if(x == fa[x])
		return x;
	return fa[x] = get(fa[x]);//路径压缩,fa 直接赋值为代表元素
}

路径压缩:在每次执行 Get 操作的同时,把访问得到每个节点(也就是所查询元素的全部祖先)都直接指向树根。如下图:
路径压缩图示

  1. 并查集的 Merge 操作
    合并元素 x 和元素 y 所在的集合,等价于让 x 的树根作为 y 的树根的子节点。
void merge(int x, int y){
	fa[get(x)] = get(y);
}

 
 

并查集图解

图解

  当下社会形式不断内卷,许多的学生组成了内卷团伙,使内卷更加有效果。由于内卷人数过于庞大,内卷频度高,学校反内卷处想查清楚到底有几个内卷团伙,反卷处搜集到了一些线索。
  现有 7 个卷王。
  1 号卷王与 2 号卷王是团伙。
  3 号卷王与 4 号卷王是团伙。
  5 号卷王与 2 号卷王是团伙。
  4 号卷王与 6 号卷王是团伙。
  2 号卷王与 6 号卷王是团伙。
  1 号卷王与 6 号卷王是团伙。
  卷王的团伙的团伙也是团伙。
  首先我们假设这 7 个卷王互相是不认识的,我们各自为政,每个人都是头头,他们都是自己卷。我们通过线索一步步合并团伙。
  第一步:申请一个一维数组 fa,用 fa[1]~fa[7] 分别存储 1~7 号卷王中每个卷王的头头是谁。
  第二步:初始化。7 个卷王各自为战,每个卷王的头头就是自己。“1 号卷王”的头头就是“1 号卷王”。以此类推,“7 号卷王”的头头是“7 号卷王”,即 fa[7] 的值为 7。
初始化
  第三步:开始合并团伙。
  第一条线索:1 号卷王与 2 号卷王是团伙。(我们规定左边为大,所以 1 号卷王是卷王的头头)
第一条线索
  第二条线索:3 号卷王与 4 号卷王是团伙。
第二条线索

  第三条线索:5 号卷王与 2 号卷王是团伙。这个时候就出现了一个矛盾 2 号卷王应该归顺 5 号还是 1 号。这是 5 号可以使 2 号的头 1 号归顺自己,那么 2 号也就是归顺自己的,即将 fa[1] 的值改为 5。
第三条线索
  第四条线索:4 号卷王与 6 号卷王是团伙。fa[4] 的值是 3,fa[6] 的值是 6。所以让 6 号卷王加入 3 号卷王团伙,即将 fa[6] 的值改为 3。
第四条线索
  第五条线索:2 号卷王与 6 号卷王是团伙。fa[2] 的值是 1,fa[1] 的值是 5,即 2 号卷王的大头头是 5 号卷王。fa[6] 的值是 3,6 号卷王的头头是 3 号卷王。我们让 6 号卷王的头头 3 号卷王归顺 2 号卷王的大头头 5 号卷王。 即将 fa[3] 的值改为 5,也就是 3 号卷王带着手下的卷王都归顺了 5 号卷王。
第五条线索
  第六条线索:1 号卷王与 6 号卷王是团伙。1 号卷王与 2 号卷王的大头头都是 5 号卷王。这又是一次路径压缩的过程。6 号卷王在寻找大头头 5 号卷王的时候,被大头头 5 号卷王直接领导了。
第六条线索
  所有线索分析完毕,看看有几个团伙?如果 fa[i]=i,就表示此人是一个卷王团伙的最高领导人。最后数组中 fa[5]=5、fa[7]=7
最后
 
 

代码

#include<stdio.h>
int fa[1001]={0},n,m,sum=0;

void init(){//这里是初始化,非常地重要存的是自己数组下标的编号就好 
	for(int i = 1; i <= n; i++)
		fa[i] = i;
	return; 
}

int getfa(int v){//找最大头头的递归函数 
	if(fa[v] == v)
		return v;
	else{
		fa[v] = getfa(fa[v]);//路径压缩 
		return fa[v];
	}
}

void merge(int v, int u){
	int t1 = getfa(v), t2 = getfa(u);//获得v,u的最大头头
	if(t1 != t2){//判断v,u是否在一个集合中 
		fa[t2] = t1;//把左边的当成领导 
	} 
	return; 
}

int main(){
	scanf("%d %d",&n,&m);
	int x,y;
	
	init();//初始化
	for(int i = 1; i <= m; i++){
		scanf("%d %d",&x,&y);
		merge(x,y);//合并团伙 
	} 
	
	for(int i = 1; i <= n; i++)//检测有多少个团伙 
		if(fa[i] == i)
			sum++;
	
	printf("%d\n",sum);
	return 0;
}

 
 

例题

第一题

题目链接
题目描述

输入
6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6
输出
Yes
Yes
No

#include<stdio.h>
int fa[10001]={0},n,m,p;

void init(){//这里是初始化,非常地重要存的是自己数组下标的编号就好 
	for(int i = 1; i <= n; i++)
		fa[i] = i;
	return; 
}

int getfa(int v){//找最大头头的递归函数 
	if(fa[v] == v)
		return v;
	else{
		fa[v] = getfa(fa[v]);//路径压缩 
		return fa[v];
	}
}

void merge(int v, int u){
	int t1 = getfa(v), t2 = getfa(u);//获得v,u的最大头头
	if(t1 != t2){//判断v,u是否在一个集合中 
		fa[t2] = t1;//把左边的当成领导 
	} 
	return; 
}

int find(int v, int u){
	int t1 = getfa(v), t2 = getfa(u);//获得v,u的最大头头
	if(t1 != t2)
		return 0;
	return 1;
}

int main(){
	scanf("%d %d %d",&n,&m,&p);
	int x,y;
	
	init();//初始化
	for(int i = 1; i <= m; i++){
		scanf("%d %d",&x,&y);
		merge(x,y);//合并团伙 
	} 
	
	for(int i = 1; i <= p; i++){
		scanf("%d %d",&x,&y);
		if(find(x,y) == 1)
			printf("Yes\n");
		else
			printf("No\n");
	}
	return 0;
}

 
 

第二题

题目链接
题目描述

输入
3
2 4 1
0 0 1
0 0 3
2 5 1
0 0 1
0 0 4
2 5 2
0 0 2
2 0 4
输出
Yes
No
Yes

#include<stdio.h>
int fa[10001]={0},n,h;
long long r,x[100001],y[100001],z[100001];
int f1[100001],f2[100001];//f1记录与顶面相交的序号,f2记录与 底面相交的序号 

void init(){//这里是初始化,非常地重要存的是自己数组下标的编号就好 
	for(int i = 1; i <= n; i++)
		fa[i] = i;
	return; 
}

int getfa(int v){//找最大头头的递归函数 
	if(fa[v] == v)
		return v;
	else{
		fa[v] = getfa(fa[v]);//路径压缩 
		return fa[v];
	}
}

void merge(int v, int u){//合并v,u 
	int t1 = getfa(v), t2 = getfa(u);//获得v,u的最大头头
	if(t1 != t2){//判断v,u是否在一个集合中 
		fa[t2] = t1;//把左边的当成领导 
	} 
	return; 
}

int find(int v, int u){//v,u是否在一个集合 
	int t1 = getfa(v), t2 = getfa(u);//获得v,u的最大头头
	if(t1 != t2)
		return 0;
	return 1;
}

long long dis(long long x, long long y, long long z, long long x1, long long y1, long long z1){//计算距离 
	return (x-x1)*(x-x1)+(y-y1)*(y-y1)+(z-z1)*(z-z1);
} 

int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%d %d %lld",&n,&h,&r);
		init();
		int t1 = 0, t2 = 0;//t1记录与顶面相交的洞,t2记录与底面相交的洞 
		for(int i = 1; i <= n; i++){
			scanf("%lld %lld %lld",&x[i],&y[i],&z[i]);
			if(z[i]+r >= h){//该圆是否与顶面相交 
				t1 ++;
				f1[t1] = i;
			}
			if(z[i]-r <= 0){//该圆是否与底面相交 
				t2 ++;
				f2[t2] = i;
			}
			for(int j = 1; j <= i; j++){//枚举之前的洞是否于这个洞相交 
				if(dis(x[i],y[i],z[i],x[j],y[j],z[j]) <= 4*r*r){//相交,则合并集合 
					merge(i,j);
				}
			}
		}
		int flag  = 0;
		for(int i = 1; i <= t1; i++){//看看是否有连通的洞 
			for(int j = 1; j <= t2; j++)
				if(find(f1[i],f2[j]) == 1){
					flag = 1;
					break;
				}
			if(flag == 1)
				break;
		}
		if(flag)
			printf("Yes\n");
		else
			printf("No\n");
	}
	
	return 0;
}

 
 

第三题

题目链接
题目描述

输入
5 4
16
2 3
1 5
5 9
4 8
7 8
9 10
10 11
11 12
10 14
12 16
14 18
17 18
15 19
19 20
9 13
13 17
输出
5

#include<stdio.h>
int fa[1000001]={0},n,m,k;

void init(){//这里是初始化,非常地重要存的是自己数组下标的编号就好 
	for(int i = 1; i <= n*m; i++)
		fa[i] = i;
	return; 
}

int getfa(int v){//找最大头头的递归函数 
	if(fa[v] == v)
		return v;
	else{
		fa[v] = getfa(fa[v]);//路径压缩 
		return fa[v];
	}
}

void merge(int v, int u){//合并v,u 
	int t1 = getfa(v), t2 = getfa(u);//获得v,u的最大头头
	if(t1 != t2){//判断v,u是否在一个集合中 
		fa[t2] = t1;//把左边的当成领导 
	} 
	return; 
}

int find(int v, int u){//v,u是否在一个集合 
	int t1 = getfa(v), t2 = getfa(u);//获得v,u的最大头头
	if(t1 != t2)
		return 0;
	return 1;
}

int main(){
	scanf("%d %d %d",&m,&n,&k);
	init();
	for(int i = 0; i < k; i++){
		int a,b;
		scanf("%d %d",&a,&b);
		merge(a,b);
	}
	int sum = 0;
	for(int i = 1; i <= n*m; i++)
		if(fa[i] == i)
			sum ++;
	printf("%d",sum);
	
	return 0;
}

 
 

第四题

题目链接
题目描述
题目描述
  采用逆向修建桥的思路,我们根据桥的有效天数进行递减排序。然后依次进行建桥,若该天建的桥的两个岛原本不连通,则该天的后一天会出现争议。

#include<stdio.h>
#include<stdlib.h>
struct Bridge{
	int x,y;
	int day;
	Bridge(){}
	Bridge(int a, int b, int c):x(a),y(b),day(c){}
};
Bridge bridge[100010];//存储所有桥 
int fa[100010]={0},n,m;//存储岛的上级 

void init(){//这里是初始化,非常地重要存的是自己数组下标的编号就好 
	for(int i = 1; i <= n; i++)
		fa[i] = i;
	return; 
}

int getfa(int v){//找最大头头的递归函数 
	if(fa[v] == v)
		return v;
	else{
		fa[v] = getfa(fa[v]);//路径压缩 
		return fa[v];
	}
}

int merge(int v, int u){//合并v,u 
	int t1 = getfa(v), t2 = getfa(u);//获得v,u的最大头头
	if(t1 != t2){//判断v,u是否在一个集合中 
		fa[t2] = t1;//把左边的当成领导 
		return 1;
	} 
	return 0; 
}

int cmp(const void *a, const void *b){//排序规则 
	struct Bridge *aa = (Bridge *)a;
	struct Bridge *bb = (Bridge *)b;
	return ((aa->day) > (bb->day)) ? -1:1;
}

int main(){
	scanf("%d %d",&n,&m);
	init();
	for(int i = 1; i <= m; i++){
		int a,b,t;
		scanf("%d %d %d",&a,&b,&t);
		bridge[i] = Bridge(a,b,t);
	}
	qsort(bridge+1,m,sizeof(bridge[1]),cmp);//day递减,快排 
	
	int sum = 0, lastDay = 0;
	for(int i = 1; i <= m; i++){
		if(merge(bridge[i].x,bridge[i].y) == 1 && bridge[i].day != lastDay){//岛x、y未连通,此桥的天数是第一次出现 
			sum++;
			lastDay = bridge[i].day;
		}
		
	}
	printf("%d",sum);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
并查集(Disjoint Set)是一种数据结构,用于解决集合的合并和查找问题。在Python中可以使用类来实现并查集。引用展示了一个简单的并查集类的代码实现,其中包括了初始化集合、查找集合、合并集合和判断两个元素是否在同一个集合中的方法。另外,引用和展示了对并查集代码的优化,包括路径压缩和按秩合并等技巧,以提高并查集的效率。 在Python中使用并查集可以解决一些实际问题,如求解岛屿个数、朋友圈等。通过将问题转化为集合的合并和查找操作,可以使用并查集来高效地解决这些问题。 所以,如果你需要在Python中实现并查集,可以参考以上的代码实现和优化方法。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [python 数据结构算法——并查集](https://blog.csdn.net/itnerd/article/details/103916115)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [并查集Python版](https://blog.csdn.net/XZ2585458279/article/details/127274576)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值