目录
并查集
并查集是一种树形的数据结构, 它用于处理一些不交集的合并及查询问题,它支持两种操作:
- 查找(Find):确定某个元素处于哪个子集
- 合并(Union):将两个子集合合并成一个集合
初始化
void makeSet(int size)
{
for(int i=0;i<size;i++)
{
fa[i]=i;//i就在它本身的集合里
}
return;
}
查找
原理
几个家族中的人忘记了自己的亲人,只记得自己的父亲是谁了,最长者(祖先)的父亲已经去世,只知道自己是祖先,为了确定自己在哪个家族,他们想出了一个办法,只要问自己的爸爸是不是祖先,一层一层的向上问,直到问到祖先。如果要判断两人是否在同一家族,只要看两人的祖先是不是同一人就可以了。
int fa[MAXN];//记录某个人的爸爸是谁,特别规定,祖先的爸爸是他自己
int find(int x)//递归方式
{
if(fa[x]==x)
{
if(fa[x]==x)
{
return x;//如果x是祖先则返回
}
else
{
return find(fa[x]);//如果不是则x的爸爸问x的爷爷
}
}
}
int find(int x)
{
while(x!=fa[x])//如果x不是祖先,就一直往上一辈找
{
x=fa[x];
}
return x;//如果x是祖先则返回
}
路径压缩
我的祖先是谁与我的父亲是谁没什么关系,不如我直接当祖先的儿子,问一次就可以出结果了,只要这个人可以代表我们家族就能得到想要的效果,把在路径上的每个节点都直接连接到根上,这就是路径压缩。
int find(int x)
{
if(x!=fa[x])//x不是自身的父亲
{
fa[x]=find(fa[x]);//查找x的祖先直到找到代表,顺手路径压缩
}
return fa[x];
}
合并
宴会上,一个家族的祖先突然对另一个家族说:"我们两个家族交情这么好,不如合成一家好了",另一个家族也欣然接受了,因为我们并不在意祖先究竟是谁,所以只要其中一个祖先变成另一个祖先的儿子就可以了。
void unionSet(int x,int y)//x与y所在家族合并
{
x=find(x);
y=find(y);
fa[x]=y;//把x的祖先变成y的祖先的儿子
}
启发式合并(按秩合并)
一个祖先突然抖了个机灵:你们家族人比较少,搬家到我们家族里比较方便,我们要是搬过去的话太费事了。
由于需要我们的支持的只有集合的合并、查询操作,当我们需要将两个集合合二为一时,无论将哪个集合连接到另一个集合的下面,都能得到正确的结果,但不同的连接方法存在时间复杂度的差异,具体来说,如果我们将一棵点数与深度都较小的集合树连接到一棵更大的集合树下,显然相比于另一种连接方案,接下来执行查找操作的用时更小(也会带来更优的更坏时间复杂度)
当然,我们不总能遇到恰好如上所述的集合(点数与深度都更小),鉴于点数与深度这两个特征都很容易维护,我们常常从中择一,作为估价函数,而无论选择哪一个,时间复杂度都为。