数据结构---并查集

在培训的时候讲到过这个东西,当时是在讲迪杰斯特拉算法的时候,没做重点,没想到校赛模拟的时候又考到了,于是花了一点时间研究了一下。

并查集是一种树状结构,用来判断连通分支数很好用(离散刚学过),所谓的连通分支数简单来说就是就是给你一些点,和各个点的连通情况,让你判断有谁和谁连在一起,或者是这个连通分支上有多少个点,又或者一共有多少个连通分支。

并查集还用来路径压缩,这里就不放图了,好多大佬讲的很好我也是看了才明白的。可以简理解为找老大,先初始化化每个人是自己的老大,然后通过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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值