1.前言
1.1并查集的概念
并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。–百度百科
1.2并查集的引入
在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中。其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
1.3并查集的主要操作
- 合并
- 查询
通过主要操作也就不难理解并查集名字的由来——对于集合元素的查询,以及对于集合的合并。称作并查集。
2.并查集的代码实现
2.1初始化
我们定义一个fa数组,fa数组用来存储下标元素的父亲结点。
对其进行初始化。在开始每一个元素的父亲结点就是它本身(我父亲竟是我自己 )qaq。
int fa[10000]
for (int i = 1;i <= 9999;i++)
fa[i]=i;
2.2合并
合并操作其实就是更改fa数组中存的内容。
比如合并1和2,就是让fa[1]=2;
这并不是完整的合并操作,下面讲完查询操作会做解释。
代码实现
void merge(int a, int b)
{
fa[a]=b;
}
2.3查询
在查询操作中,其实要查询的是每个元素的祖先结点(如果只是要查询父亲结点可以直接输出fa[x]),如果fa[x]等于x那么x就是根节点(在没有合并之前的每一个点都是根节点)如果fa[x]!=x那么就查询fa[x]的父亲的父亲。所以其实要用的就是递归。
代码实现
int hp(int x)
{
if(fa[x]==x)
return x;
return hp(fa[x]);
有的人可能会发现这个算法并不节省时间,因为每一次查询都要往上不断的查询。如果我有q次查询,每一次都查询树的深度n,那么它的时间复杂度就是q*n。如果q=n,那么时间复杂度就是O(n^2)。这并不够优化。
甚至可以说是跟没用一样
那么该怎么优化呢。。。
就是要在查询的过程中不断更新,
即每次查询都要让fa[x]=hp(fa[x])。
其实就是套娃就是原本只是储存x的父亲结点的fa[x]更新为储存fa[x]的父亲结点,在每一次递归时,最后都会把当前路线上的所有元素的fa[x]的值更新为根节点。
什么意思呢?就是比如说现在有这么一条路径(1是根节点)
1-2-3-4-5
查询5的祖先,开始我们发现fa[x]=4!=x=5,那么我们要递归查询fa[x],递归时我们return fa[x]=hp(fa[x]).
第一次递归,我们发现fa[x]=3!=x=4,继续return fa[x]=hp(fa[x]).
第二次递归,我们发现fa[x]=2!=x=3,继续return fa[x]=hp(fa[x]).
第三次递归,我们发现fa[x]=1!=x=2,继续return fa[x]=hp(fa[x]).
第四次递归,终于fa[x]=x=1。返回x的值1.
退回第三次递归,fa[2]=1.
退回第二次递归,fa[3]=1.
退回第四次递归,fa[4]=1.
返回函数当中fa[5]=1.
最后的返回值为1.
5的祖先是1.
int hp(int x)
{
if(fa[x] == x) return x;
else {
return fa[x] = hp(fa[x]);
}
2.4操作的结合
上面对于初始化,合并,以及查询操作都进行了单独的解释。
那么把他们组合到一起该怎么做呢?
首先初始化是肯定要做的否则可能会找不到祖先
然后就是查询与合并操作。
上面说到合并操作并不完整,这里做出解释。
首先还是按照上面写的写好查询操作。
现在要写合并操作了。
请大家回想一下,刚刚写查询操作的时候,我们是不是对于fa数组在查询过程中进行了更新。那么合并操作是否也需要更新呢?我们再来看一看代码。
void merge(int a, int b)
{
fa[a]=b;
}
a与b合并的时候,只是让fa[a]=b。但是如果此时我们要查询a的祖先的时候,如果fa[b]!=b,我们还需要继续向上查询,并且更新。所以在合并的时候也是要进行更新的。
void merge(int a, int b)
{
fa[hp(a)]=hp(b);
}
就是对与a的祖先结点进行变更为b的祖先结点,就可以把他们并入一个集合。
并查集的完整代码
#include <bits/stdc++.h>
int fa[10000];
int hp(int x)
{
if(fa[x] == x) return x;
else {
return fa[x] = hp(fa[x]);
}
}
int marage(int x,int y)
{
fa[hp(x)] = hp(y);
}
int main(){
for (int i = 0; i <= 10000; i++)
fa[i] = i;
return 0;
}
根据不同的题对于并查集的内容可能会有更改,以及不同的应用。
3例题演练
3.1并查集模板题
题目来源洛谷P3367
没有什么好说的就是一道简单的模板题
3.2亲戚
题目来源洛谷P1551
3.3朋友
题意两个公司,一个公司全是男的,一个公司全是女的。
给你一些关系表示是朋友。
问你男公司与小明(用1表示)是朋友关系的人,与女公司小红(用-1表示)是朋友关系的人可以凑成多少对情侣。
男用正数表示,女用负数表示。
题解用并查集合并之后,再查询。
就这样去写吧,交了之后wrong answer,或者run time error.
那是为什么呢?
注意数组中下标没有用负数表示的。
人傻了,找到了方法,不知道该怎么处理了。
可以变通一下嘛QAQ。。。
对于负数表示的,我们用n+1到n+m表示就可以了!(给负数*-1+n)
处理好关系之后,分别统计对于小明,hp(x)==fa[1]有多少个,对于小红,从n+1到n+m,hp(x)==fa(n+1)有多少个。
二者取最小值,输出即可。