目录
1.查:寻找一个元素归属于哪个集合或者说判断两个元素是否同属于一 个集合(或者说是是否属于同一个根节点)
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;
}