普通并查集
简介
在平时的计算中,常常会遇到集合划分的问题,例如一个集合S={a1,a2,a3,a4},按照一定规则我们可以划分为S1={a1,a2},S2={a3},s3={a4}。但是在划分好集合后,又该如何快速确认任意两个元素之间的关系呢。由此引出并查集。
并查集简介
并查集最关键的表现就是一个集合中的每一个元素都有一个共同的集合代表元素,也就是集合的boss。判断两个元素是否为同一个集合的条件便是二者的boss是否为一个人。而合并两个集合,只需要将两个集合所有元素的最终boss都设成同一个元素。
由此:并查集需要两个关键函数
- find()查找元素集合
- union()合并集合
先用图感受一下思路:
1.现在每个元素都是一家公司,它们各自都是自己的boss。
2.公司1和公司5志趣相投,决定合作。一番商讨,决定5来当boss。
3.公司3和公司4见状,两手一拍,不行!我们也得合作和那两家伙干,3当了boss。
4.公司6看腥风血雨,正好和公司4有点交情,想来寻求庇护,4成为了6的boss,3成为了第一个大boss。
5.底下其乐融融,3和5左思右想觉得再这么对着干下去不如来一波和谐的py交易,说时迟那时快,3,5决定合体!但boss只能有一个,3有管理经验,3变成了5的boss。
6.公司2的消息不灵通,市场巨变,还在自嗨,反应过来的时候,想着去求助1和6,二者告诉他他们的boss分别是5和4,2又去找5和4,发现他们的boss都是3。哦!原来都是一家人,3啊,我要做你小弟啊!
PS:然后他们就因为垄断被罚款了。
代码实现
就先来实现最简单的。
定义
int boss[1000];//这个数组记录的每个公司的boss是谁
Find
int find(int x) {
while (boss[x] != x) x = boss[x];//如果x的boss不是x自己,那么就去找x的boss
return x;
}
Union
void union(int x, int y) {
int tx = find(x);//找到x的boss
int ty = find(y);//找到y的boss
if (x != y) boss[x] = y;//不是一个boss,你就当我的boss
else return;
}
一个简单的并查集就实现了,但是这样实现的并查集树状结构不可控,一个一字长蛇阵就可以让查询的效率变得很低,所以我们希望所有子公司可以直接找到最终的boss。这就是路径压缩算法。在n次ufind的时间复杂度上界为O(n\(\alpha\)(n)),\(\alpha\)(n)是阿克曼函数的反函数。
我们先修改find函数
Find_zwei
int find_zwei(int x) {
if (boss[x] == x) return x;
return boss[x] = find_zwei(boss[x]);//你的boss就是我的boss,my~my~my~my~my~
}
我们还可以改造union函数,进一步优化这个树状结构。我们引入一个rank数组表示每一个元素的高度,初始为1。
定义_zwei
int boss[1000];
int rank[1000];
Union_zwei
void union_zwei(int x, int y) {
int tx = find_zwei(x);
int ty = find_zwei(y);
if (tx == ty) return;
if (rank[tx] > rank[ty]) boss[ty] = tx;//tx的高度大于ty的高度,也就是tx为2,ty为1的时候
else {
if (rank[tx] == rank[ty]) {//二者高度相同的时候必然会增加高度
rank[ty]++;
}
boss[tx] = ty;
}
}
初始化
void init(int n) {
for (int i = 1; i <= n; i++) {
boss[i] = i;
rank[i] = 1;
}
}
至此,就是普通并查集。后续再讲带权并查集。