在培训的时候讲到过这个东西,当时是在讲迪杰斯特拉算法的时候,没做重点,没想到校赛模拟的时候又考到了,于是花了一点时间研究了一下。
并查集是一种树状结构,用来判断连通分支数很好用(离散刚学过),所谓的连通分支数简单来说就是就是给你一些点,和各个点的连通情况,让你判断有谁和谁连在一起,或者是这个连通分支上有多少个点,又或者一共有多少个连通分支。
并查集还用来路径压缩,这里就不放图了,好多大佬讲的很好我也是看了才明白的。可以简理解为找老大,先初始化化每个人是自己的老大,然后通过join操作,使得树状的一级一级的关系图,变成星状图(类似计网里的拓扑结构)这个人作为枢纽和关键,凡是老大是他的都是相互连通的。
先来看看并查集的基本操作,然后用校赛模拟和在pta上一个题来具体分析一下。
首先要定义一个全局数组 记录每个元素的老大,并且初始化每个元素的老大是自己。
#define maxn 1001
int pre[maxn];
void init_pre()//初始化使得每个元素的老大指向自己
{
int i;
for(i=0;i<1001;i++)
pre[i]=i;
}
接着就是找老大的操作
int Find(int i)//找i元素的老大
{
if(pre[i]==i)//如果这个人老大自己就返回自己
return i;
else//不是的话就递归,找他老大的老大,如果在join操作之后,其中这步之后只要再递归一次就是了,因为大家都指向自己的老大了,再递归一次,老大的老大是自己。
return pre[i]=Find(pre[i]);
}
这个操作有很多种写法,还可以写成循环体
int Find(int i)//找i的老大
{
while(pre[i]!=i)//当i的老大不是自己时,说明他不是老大,找他老大的老大
i=pre[i];
return i;//直到找到有一个结点的pre[]数组指向自己,说明他就是老大
}
最后是一个join操作,把两人的老大指向同一个人
void join(int x,int y)
{
int fx=Find(x),fy=Find(y);//定义x的老大是fx,y的老大是fy
if(fx!=fy)//两个人老大不是同一个人时
pre[fx]=fy;//委屈fx,让fx的老大是fy,自然,x的老大也变成了fy
}
有了init,Find,join就可以做题了。
先看pta上的题目
L1-020 帅到没朋友 (20分)
当芸芸众生忙着在朋友圈中发照片的时候,总有一些人因为太帅而没有朋友。本题就要求你找出那些帅到没有朋友的人。
输入格式:
输入第一行给出一个正整数N(≤100),是已知朋友圈的个数;随后N行,每行首先给出一个正整数K(≤1000),为朋友圈中的人数,然后列出一个朋友圈内的所有人——为方便起见,每人对应一个ID号,为5位数字(从00000到99999),ID间以空格分隔;之后给出一个正整数M(≤10000),为待查询的人数;随后一行中列出M个待查询的ID,以空格分隔。
注意:没有朋友的人可以是根本没安装“朋友圈”,也可以是只有自己一个人在朋友圈的人。虽然有个别自恋狂会自己把自己反复加进朋友圈,但题目保证所有K超过1的朋友圈里都至少有2个不同的人。
输出格式:
按输入的顺序输出那些帅到没朋友的人。ID间用1个空格分隔,行的首尾不得有多余空格。如果没有人太帅,则输出No one is handsome。
注意:同一个人可以被查询多次,但只输出一次。
输入样例1:
3
3 11111 22222 55555
2 33333 44444
4 55555 66666 99999 77777
8
55555 44444 10000 88888 22222 11111 23333 88888
输出样例1:
10000 88888 23333
输入样例2:
3
3 11111 22222 55555
2 33333 44444
4 55555 66666 99999 77777
4
55555 44444 22222 11111
输出样例2:
No one is handsome
题意是,先给一个数T代表接下来输入朋友圈的个数,接下来T行,先给出这个朋友圈的人数,然后给出每个朋友圈的人的ID,然后输入n,接下来输入n个ID,查询这n个人谁(帅到)没有朋友,输出要注意,按照输入顺序,并且不重复输输出。没人没有朋友,输出No one is handsome
#include<stdio.h>
#define maxn 1000000
int pre[maxn],count[maxn],root[1001],ans[10000],b[maxn];
/*pre[]储存每个ID的老大,count用来记录每个老大有多少人和他关联,root用来记录输入的T个朋友圈的第一个人,ans是为了输出,b[]储存要查询的ID*/
void init()//初始化
{
int i;
for(i=0;i<maxn;i++)
pre[i]=i;
}
int find(int x)//找x的根节点
{
while(pre[x]!=x)//当x的根节点不是自己时,说明他不是根,找他上头的根节点
x=pre[x];
return x;//直到找到有一个结点的pre[]数组指向自己,说明他就是根节点
}
void join(int x,int y)//两个结点并到一起
{
int fx=find(x),fy=find(y);
if(fx!=fy)
pre[fx]=fy;
}
int main()
{
int T,m,i,temp,n,flag=0,j,k=1;
scanf("%d",&T);
init();
for(i=0;i<T;i++)//T个朋友圈
{
scanf("%d %d",&m,&root[i]);//输入朋友圈人数,和朋友圈第一个人
for(j=1;j<m;j++)//接下来m-1个人让他们老大都指向第一个人
{
scanf("%d",&temp);
join(temp,root[i]);
}
count[find(root[i])]++;//找到root的老大,并且记录这个老大出现过几次,意思就是这个老大合同连通的跨几个朋友圈,
}
scanf("%d",&n);
for(i=0;i<n;i++)
scanf("%d",&b[i]);
for(i=0;i<n;i++)
{
if(pre[b[i]]==b[i]&&count[b[i]]<2)//判断pre[b[i[],老大指向自己可能是没有朋友圈的人,只是初始化了,并且count[b[i]]<2,是为了判断一个朋友圈只有一个人,并且这个人没出现在其他朋友圈里
{
for(j=0;ans[j]!=0;j++)//输出格式问题,从第二个数开始,判断和之前的数是否重复,重复k=0,不输入
{
if(ans[j]==b[i])
{
k=0;break;
}
}
if(k==1)//开始k=1,第一个数不可能重复,直接加入
ans[flag++]=b[i];
}
}
if(flag==0)//说明没有数满足
printf("No one is handsome");
else//否则,一共flag数,第一个照常输出,后面的数前面加上空格
{
for(i=0;i<flag;i++)
{
if(i==0)
printf("%05d",ans[i]);
else
printf(" %05d",ans[i]);
}
}
return 0;
}
很可惜的是这个题最后一个点没过。。答案错误,没找出来错误
下面看看校赛模拟的题,考的也是并查集,给出的数据的格式都及其相似,就是问法不同,输出不同,这个题让你查询元素的连通情况,校赛模拟题是让输出连通分支数数,并且按降序输出每个分支的元素个数,感觉更简单些。
7-2 社交集群 (30分)
当你在社交网络平台注册时,一般总是被要求填写你的个人兴趣爱好,以便找到具有相同兴趣爱好的潜在的朋友。一个“社交集群”是指部分兴趣爱好相同的人的集合。你需要找出所有的社交集群。
输入格式:
输入在第一行给出一个正整数 N(≤1000),为社交网络平台注册的所有用户的人数。于是这些人从 1 到 N 编号。随后 N 行,每行按以下格式给出一个人的兴趣爱好列表:
输出格式:
首先在一行中输出不同的社交集群的个数。随后第二行按非增序输出每个集群中的人数。数字间以一个空格分隔,行末不得有多余空格。
输入样例:
8
3: 2 7 10
1: 4
2: 5 3
1: 4
1: 3
1: 4
4: 6 8 1 5
1: 4
输出样例:
3
4 3 1
#include<stdio.h>
#include<stdlib.h>
#define maxn 1005
int pre[maxn],root[maxn],cnt[maxn]={0};//pre[]记录元素的老大,root[]把每个人的第一个爱好当老大,这题是想求爱好的连通性,根据爱好的连通性就能判断这个圈子有多少人
void make_pre()//初始化化
{
int i;
for(i=0;i<1005;i++)
pre[i]=i;
}
int Find(int i)
{
if(pre[i]==i)
return i;
return
pre[i]=Find(pre[i]);
}
void join(int x,int y)//两个元素老大指向同一个
{
int fx=Find(x),fy=Find(y);
if(fx!=fy)
pre[fx]=fy;
}
int cmp(const void *a,const void *b)//比较函数,用来排序的
{
return *(int *)b-*(int *)a;
}
int main()
{
int n,i,j,m,temp;
int count=0;
scanf("%d",&n);//n个人
make_pre();//初始化pre[]
for(i=0;i<n;i++)
{
scanf("%d: ",&m);//输入多少个爱好
scanf("%d",&root[i]);//第一个爱好当老大
for(j=1;j<m;j++)
{
scanf("%d",&temp);//剩下这个人的爱好都指向第一个
join(temp,root[i]);
}
}
for(i=0;i<n;i++)//找到指向同一个老大(爱好)的有几个人
cnt[Find(root[i])]++;
for(i=0;i<1005;i++)//不等于零就说明有人,有一个人也输出
{
if(cnt[i])
count++;
}
qsort(cnt,1001,sizeof(int),cmp);//降序排序
printf("%d\n",count);//输出多少个圈子
for(i=0;i<count;i++)//每个圈子多少人
{
if(i==0)
printf("%d",cnt[i]);
else
printf(" %d",cnt[i]);
}
return 0;
}