定义
并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。---百度
并查集,顾名思义,也就是支持 “并”, “查”, “集”的一种数据结构,可以合并,查询以及集合。是一种极为重要的算法,在各个方面均有应用。
实现并查集
并查集一般支持两个操作:
- 查找祖先
- 合并集合
如下:
首先,我们需要一个 fa 数组,把 fa[i] 初始化为 i。也就是最开始的时候,他的爸爸就是他自己。
for (int i = 1; i <= n; i++) fa[i] = i;
然后,我们还要一个 Find 函数用于查找他的祖先,具体的原理就是递归实现。
注 : 我们最好不要用 find,因为它好像是一个关键字还是函数之类的东西,并且单驼峰命名也是一个好习惯
int Find (x) {
if (fa[x] != x) return Find (fa[x]);
//因为我们可以知道,一个集合的祖先的父亲肯定是没有被改变过的,也就是我们初始化时的他自己,如果这个节点的父亲还不是祖先,那么我们就要寻找他父亲的父亲(一辈一辈往上找)
else return fa[x]; //否则我们就返回他自己(边界)(此处返回 x 是一样的)
}
我们就会发现,万一有一个集合是下面的样子呢?
那么我们就会发现,我们的每一次访问都可能会浪费掉很多时间,又因为我们每一次查找的目的都是为了查找一个点的祖先,所以我们可以在查找 x 的祖先的时候,就直接把他的父亲的值直接赋成他的祖先,也就是:
int Find (x) {
if (fa[x] != x) return fa[x] = Find (fa[x]);
else return fa[x];
}
没错,就是上面的代码,只是变了一点点而已,但他却可以大大优化你的时间复杂度(大多数题都会卡你),并且他还有一个很高大上的名字 : 路径压缩
我们还需要一个 Merge 函数来进行合并两个数,原理就附在代码上
bool Merge (int x, int y) {//布尔是因为大多数题都还有其他操作,随机应变咯
x = Find (x);
y = Find (y);//查找各自的祖先
if (x == y) return 0;//假如他们就在同一个集合之中,那么不用管他们
fa[y] = x;//把 y 的父亲修改成 x (因为大多数题目中,x < y)
return 1;
}