上一篇中我们进行了散列表的相关练习,在这一篇中我们要学习的是并查集。
在许多实际应用场景中,我们需要对元素进行分组,并且在这些分组中进行查询和修改操作。比如,在图论中,我们需要将节点按照连通性进行分组,以便进行最小生成树、最短路径等算法;在计算机视觉中,我们需要将像素进行分组,以便进行图像分割和对象识别等任务。而并查集正是为了解决这些问题而被提出来的一种数据结构。
概念
并查集(Disjoint Set)是一种用于处理元素分组的数据结构,通常用于解决一些与等价关系有关的问题,比如连通性的判断、最小生成树算法中的边的合并等。
并查集中的每个元素都属于一个集合,每个集合都有一个代表元素(也称为根节点),代表元素可以用来表示整个集合。并查集支持三个基本操作:
1.MakeSet(x):创建一个只包含元素 x 的新集合;
2.Find(x):返回元素 x 所属的集合的代表元素;
3.Union(x, y):将元素 x 和 y 所属的集合合并成一个新集合。
其中,Find 操作可以使用路径压缩(Path Compression)和按秩合并(Union by Rank)优化,以提高查询效率。
并查集的应用非常广泛,比如在图论算法中求解连通性、求解最小生成树等问题时都会用到。
伪代码
// 初始化并查集,每个元素单独成集合
function MakeSet(x)
x.parent = x
x.rank = 0
// 查找元素所属的集合(根节点),并进行路径压缩
function Find(x)
if x.parent != x
x.parent = Find(x.parent) // 路径压缩:将x的父节点设为根节点
return x.parent
// 合并两个集合,按秩合并
function Union(x, y)
xRoot = Find(x)
yRoot = Find(y)
if xRoot == yRoot
return // 已经在同一个集合中,无需合并
if xRoot.rank < yRoot.rank
xRoot.parent = yRoot
else if xRoot.rank > yRoot.rank
yRoot.parent = xRoot
else
yRoot.parent = xRoot
xRoot.rank = xRoot.rank + 1
接下来,让我们进行并查集的相关练习。
选择题
1.
选B
2.
解析:
1 -4 1 1 -3 4 4 8 -2
0 1 2 3 4 5 6 7 8
1对应-4,则1是根节点且有4个子孙
又因为0、2、3都对应1
所以
1
0 2 3 null
4对应-3,则4是根节点且有3个子孙
又因为5、6都对应4
所以
4
5 6 null
8对应-2,则8是根节点且有2个子孙
又因为7对应8
所以
8
7 null
将6与8所在的集合合并,且小集合合并到大集合
则
4
5 6 8
7 null
所以树根是4,对应的编号是-5(-表示树根,5表示4的子孙个数)
3.
可以画出来对应的树
然后把小树连到大树上
接着从1到7遍历
如果有父节点,给出父节点的值
如果它本身是根节点,则给出负号和子孙个数
填空题
编程题
7-1 朋友圈
某学校有N个学生,形成M个俱乐部。每个俱乐部里的学生有着一定相似的兴趣爱好,形成一个朋友圈。一个学生可以同时属于若干个不同的俱乐部。根据“我的朋友的朋友也是我的朋友”这个推论可以得出,如果A和B是朋友,且B和C是朋友,则A和C也是朋友。请编写程序计算最大朋友圈中有多少人。
输入格式:
输入的第一行包含两个正整数N(≤30000)和M(≤1000),分别代表学校的学生总数和俱乐部的个数。后面的M行每行按以下格式给出1个俱乐部的信息,其中学生从1~N编号:
第i个俱乐部的人数Mi(空格)学生1(空格)学生2 … 学生Mi
输出格式:
输出给出一个整数,表示在最大朋友圈中有多少人。
输入样例:
7 4
3 1 2 3
2 1 4
3 5 6 7
1 6
输出样例:
4
#include<stdio.h>
int a[30001]; // 定义数组a,用于存储并查集的父节点信息
int search(int b){
// 查找元素所属的集合(根节点)
if(a[b]<0){
// 如果a[b]小于0,说明b是根节点
return b; // 返回b作为集合的代表元素
}else{
return search(a[b]); // 否则递归查找父节点,直到找到根节点
}
}
void function(int m,int n){
// 合并两个集合
int x,y;
x=search(m); // 查找m所属的集合(根节点)
y=search(n); // 查找n所属的集合(根节点)
if(x!=y){
// 如果m和n不在同一个集合中
a[x]+=a[y]; // 将集合y的大小加到集合x上
a[y]=x; // 将集合y的父节点指向集合x
}
}
int main(){
int m,n;
scanf("%d %d",&n,&m); // 输入学生数量n和关系数量m
int i;
for(i=0;i<=n;i++){
// 初始化并查集,每个元素单独成集合
a[i]=-1; // 初始时每个元素的父节点为自身,且集合大小为1
}
int stu,j,num,num1;
for(i=0;i<m;i++){
// 处理每组关系
scanf("%d",&stu); // 输入每组关系中学生的数量
for(j=0;j<stu;j++){
// 输入每组关系中的学生编号
scanf("%d",&num);
if(j==0){
num1=num; // 记录第一个学生的编号
}else{
function(num1,num); // 合并这组关系中的学生
}
}
}
int min;
min=a[1];
for(i=2;i<n;i++){<