上一期我给大家分享了法一解法,这次给大家带来一种讨巧的解法。(题目见上一期文章)
法二:这种解法讨巧就讨巧在集合合并这一操作上。(这边照样一笔带过,详解请见代码二)
#include <stdio.h>
#define MAX 30001//学校学生人数n最多为30000。
int parent[MAX];//定义一个全局数组parent[],用来记录每个元素的根结点。
/*初始化操作*/
void init(int n)
{
int i;
for(i=0;i<n;i++)
parent[i]=i;//将每个结点的父结点设置成自己(学生从0——n-1编号)。
}
/*查询元素的根结点操作*/
int Find(int i)
{
if(parent[i]==i)//如果该元素的父结点就是其自己,则说明该元素为根结点,返回该元素的编号。
return i;
else
/*否则递归地去寻找该元素的根结点,
递归操作顺便把从根到该结点路径上的所有结点都直接放到根节点下。*/
return parent[i]=Find(parent[i]);//压缩路径。
//补充:压缩路径的好处:有可能降低了树高,提高以后的查询效率。
}
/*集合合并操作*/
/*找到元素a的根结点A,元素b的根结点B,
如果A<B,则将B的父结点设置为A,如果A>B,则将A的父结点设置为B,
如果A和B相等,则说明元素a和元素b在同一个集合中,此时不需要进行集合合并操作。
这样操作以后,和编号为0的学生有类似“朋友圈”性质的学生便都合并在以编号为0的学生为根结点的集合中,
便于输出,详细操作请见下文。*/
void Union(int a,int b)
{
int A=Find(a);
int B=Find(b);
if(A<B)
parent[B]=A;
else if(A>B)
parent[A]=B;
else
return;
}
int main(int argc, char *argv[]) {
int n,m,i;
while((scanf("%d%d",&n,&m))&&n!=0&&m!=0)//循环输入。
{
init(n);//对n个集合进行初始化(学生编号0——n-1,一开始将每个学生看成互不相交的单元素集合)。
while(m--)//m个社团。
{
int a[MAX],k;//定义一个一维数组,记录社团里的学生编号。
scanf("%d",&k);//输入社团学生人数。
for(i=0;i<k;i++)
scanf("%d",&a[i]);//输入k个学生的编号,用一维数组进行存储。
for(i=1;i<k;i++)
Union(a[0],a[i]);//将一维数组中的第一个元素和该数组中剩余的元素依次进行集合合并操作。
}
int count=1;//定义一个计数器count,并赋初值为1。
/*(学生编号从0-n-1)通过循环去找学生编号为1-n-1的根结点,
并判断该学生的根结点是否为0,是的话,则将计数器进行自增操作。*/
for(i=1;i<n;i++)
{
if(Find(i)==0)
count++;
}
printf("%d\n",count);//通过上述循环,count即表示以0为根结点的集合元素个数。
}
}
这道题目主要涉及到并查集这一知识点,有不清楚的小伙伴可以点下方的传送门前往b站充电。
传送门:https://www.bilibili.com/video/BV13t411v7Fs。(全网最好的并查集讲解视频,个人感觉)。
好的,这期分享就先到这里,感谢大家的捧场!