并查集

本文详细介绍了并查集的数据结构、主要操作(查和并)、时间复杂度分析以及在实际问题中的应用,包括模板题、亲戚问题、朋友关系和搭配购买场景。
摘要由CSDN通过智能技术生成

目录

1. 并查集的基本介绍

2.“并”和“查”

1.查:寻找一个元素归属于哪个集合或者说判断两个元素是否同属于一 个集合(或者说是是否属于同一个根节点)

2.并:将两个子树并在一起,通常是将小子树并在大子树上面

3.对应的时间复杂度

4.“并”的优化

3.并查集的实战应用

1.题目:【模板】并查集

2.题目:亲戚

3.题目:朋友

4.题目:搭配购买


1. 并查集的基本介绍


定义:
并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。比如说,我们可以用并查集来判断一个森林中有几棵树、某个节点是否属于某棵树等。

主要构成:
并查集主要由一个整型数组s[ ]和两个函数find( )、union( )构成。(查和并)
数组 s[ ] 记录了每个点的前驱节点是谁,函数 find(x) 用于查找指定节点 x 属于哪个集合,函数 union(x,y) 用于合并两个节点 x 和 y 。

作用:
并查集的主要作用是求连通分支数(如果一个图中所有点都存在可达关系(直接或间接相连),则此图的连通分支数为1;如果此图有两大子图各自全部可达,则此图的连通分支数为2……)

2.“并”和“查”

1.查:寻找一个元素归属于哪个集合或者说判断两个元素是否同属于一 个集合(或者说是是否属于同一个根节点)
int find(int x)
{
    while(s[x]>=0)//当指针不为-1时就会一直向前搜索,直到搜索出根结点
        x=s[x];
    return x;//返回根结点的下标
}
2.并:将两个子树并在一起,通常是将小子树并在大子树上面
void bing(int root1,int root2)
{
    if(root1==root2) return;//传进来两个根节点一样的本来就是本身,不用合并,直接返回就好
    s[root2]=root1;//将子树2的指针指向子树1的下标
}

如图所示,其中s[]表示对应数组元素的根节点,下图片代码s数组不是定义全局变量,但在上面代码中我写的全局变量的代码,复制粘贴的时候请注意。

3.对应的时间复杂度

如下图所示,下面的root1,root2是指根节点,并且作业不会保证小树连接到大树上,也有可能是大树连接到小树上去,并且如果数据量过大,所花时间会大幅度增多。所以有改进样品。

4.“并”的优化

优化代码即图片如下所示,依然是定义全局变量。

void bing(int root1,int root2) {
	if(root1=root2)return;
	if(s[root2]>=s[root1]) {        //注意看绝对值
		s[root1]+=s[root2];
		s[root2]=root1;
	} else {
		s[root2]+=s[root1];
		s[root1]=root2;
	}
}

为了方便理解,可观看如下图片。

3.并查集的实战应用

写题中可能也会遇到不一样的查和并,我记得这种也挺好用的。上面方法中根节点的元素是负数,其他则是根节点的下标,如果根节点是1,子节点是2,3,4,5,s[1]=-5,包括自己在内,有5个关联元素。而下面这种则是s[1]=1,s[2]=1,s[3]=1,s[4]=1,s[5]=1.稍微有点不一样。

查:

int find(int x) {
	if(x!=s[x])
		s[x]=find(s[x]);
	return s[x];
}

并:

void bing(int root1,int root2) {
	if(root1==root2) {
		return ;
	} else {
		if(root1>root2)
			s[root1]=root2;
		else
			s[root2]=root1;
	}
}
1.题目:【模板】并查集

输入:

4 7
2 1 2
1 1 2
2 1 2
1 3 4
2 1 4
1 2 3
2 1 4

输出:

N
Y
N
Y

注意:和并树的时候用的是根节点,所以我也有点小失误,后面的就是按着题目意思来就可以了。

还要我的题解涉及的选择也可以用这个函数的选择性使用

#include<bits/stdc++.h>
using namespace std;
int s[200005];
int n,m;
int fin(int x) {
	while(s[x]>=0)//当指针不为-1时就会一直向前搜索,直到搜索出根结点
		x=s[x];
	return x;//返回根结点的下标
}
void bing(int root1,int root2) { //并
	if(root1==root2)
		return;
	s[root2]=root1;
}

int main() {
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)
		s[i]=-1;
	while(m--) {
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		if(a==1) {
			bing(fin(b),fin(c)); //和并树的时候用的是根节点
		}
		if(a==2) {
			if(fin(b)==fin(c))
				printf("Y\n");
			else
				printf("N\n");
		}
	}
	return 0;
}
2.题目:亲戚

输入:

6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6

输出:

Yes
Yes
No
#include<bits/stdc++.h>
using namespace std;
int s[200005];
int n,m,t;
int fin(int x) {
	while(s[x]>=0)//当指针不为-1时就会一直向前搜索,直到搜索出根结点
		x=s[x];
	return x;//返回根结点的下标
}
void bing(int root1,int root2) { //并
	if(root1==root2)
		return;
	s[root2]=root1;
}

int main() {
	scanf("%d %d %d",&n,&m,&t);
	for(int i=1; i<=n; i++)
		s[i]=-1;
	while(m--) {
		int a,b,c;
		scanf("%d %d",&a,&b);
		bing(fin(a),fin(b));
	}
	int r,e;
	for(int i=1; i<=t; i++) {
		scanf("%d %d",&r,&e);
		if(fin(r)==fin(e))
			printf("Yes\n");
		else
			printf("No\n");
	}
	return 0;
}

3.题目:朋友

输入:

4 3 4 2
1 1
1 2
2 3
1 3
-1 -2
-3 -3

输出:

2

稍微动点脑子,想到了的话,过程也是很简单的。

#include<bits/stdc++.h>
using namespace std;
int n,m,p,q,x,y;
int a[20005];
int b[20005];
int find(int x,int s[]) { //查找根节点
	while(s[x]>=0)
		x=s[x];
	return x;
}

void bing(int root1,int root2,int z[]) { //并,和
	if(root1==root2)
		return ;
	if(root1==1)
		z[root2]=root1;
	else if(root2==1)
		z[root1]=root2;
	else
		z[root2]=root1;
}
int main() {
	cin>>n>>m>>p>>q;            //公司A,公司B,A公司p对朋友关系,B公司q对朋友关系
	for(int i=1; i<=n; i++)
		a[i]=-1;
	for(int i=1; i<=m; i++)
		b[i]=-1;

	for(int i=0; i<p; i++) {
		cin>>x>>y;
		bing(find(x,a),find(y,a),a);
	}

	for(int i=0; i<q; i++) {
		cin>>x>>y;
		bing(find(-x,b),find(-y,b),b);  //注意这里是负号
	}

	int k=0,g=0;
	for(int i=1; i<=n; i++)             //只要是根节点为1,++
		if(find(i,a)==1)
			k++;
	for(int i=1; i<=m; i++)
		if(find(i,b)==1)
			g++;

	printf("%d",min(g,k));        //头文件,所以可以直接用
	return 0;
}

4.题目:搭配购买

输入:

5 3 10
3 10
3 10
3 10
5 100
10 1
1 3
3 2
4 2

输出:

1

这道题目的算法便签是并查集,背包,强连通分量。虽然写出来了,但是没用到强连通分量,主体还是并查集和背包的结合,注意我并查集的使用,与之前稍微有点不一样了,有点灵活变通了。

#include<bits/stdc++.h>
using namespace std;
int n,m,t;
int jin[10005],ja[10005];//金钱,价值
int s[10005];            //并查集标记根节点
int w[10005],v[10005];//统计,重量
int dp[10005];          //dp
int find(int x) {
	if(s[x]==x)
		return x;
	s[x]=find(s[x]);
	return s[x];
}
void bing(int root1,int root2) {
	if(root1==root2) {
		return ;
	} else {
		if(root1>root2)
			s[root1]=root2;
		else
			s[root2]=root1;
	}
}
int main() {
	cin>>n>>m>>t;   //n云,m搭配,有的钱
	for(int i=1; i<=n; i++)
		s[i]=i;
	for(int i=1; i<=n; i++)
		cin>>jin[i]>>ja[i];

	while(m--) {                 //二叉树,划范围
		int e,r;
		cin>>e>>r;
		bing(find(e),find(r));
	}
	for(int i=1; i<=n; i++) {  //相当于01背包问题的体积和重量
		w[find(i)]+=jin[i];//钱,体积
		v[find(i)]+=ja[i];//价值,重量
	}

	for(int i=0; i<=n; i++) {            //虽然中间有的数是0,但是也无伤大雅
		for(int j=t; j>=w[i]; j--) {
			dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
		}
	}
	printf("%d\n",dp[t]);
//    for(int i=1;i<=n;i++)   //验证根节点是否正确
//    printf("%d ",s[i]);
	return 0;
}

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值