概念
并查集(Disjoint Sets),字面意思是一种集合。这个集合具有的功能是合并和查找
合并(Union):将两个集合合并成一个集合。
查找(Find):判断两个元素是否在一个集合。
并查集使用数组就可以实现:parent[]。
图的环问题
如果是我们想要判断一个无向图是否存在环,我们应该如何做呢?
由这个问题正式引出并查集:
首先,以上提到的并查集是一个树状的结构,其中parent数组:parent[i] = j 的含义就是 parent of i is j; 即i的双亲是j。
我们通过一个树状的结构来判断图是否存在环:遍历边的过程中,只要有边两端的结点在同一个集合中,则必然有环。
其实,树是可以看出一个特殊的图。如果说一个图要变成一个树,他首先要去掉其中“多余”的边。其实这个并查集就是用一个树结构铺在图上,那么这个图中“多余”的边也就一目了然了。
或许还可以联系到就是一个重要最小生成树算法——Kruskal算法
并查集的基本操作
初试化双亲数组
for(int i = 0; i < n; i++)
parent[i] = i;
实际上你赋初值为-1也可以。
查找
并查集本身是一个树结构,那么必然一个集合只有一个根节点。所以查找操作就是对于给定的节点寻找其根节点
//非递归实现
int FindParent(int x)
{
while(x != parent[x])
x = parent[x];
return x;
}
//递归实现
int FindParent(int x)
{
if(x == parent[x])
return x;
else
return FindParent(parent[x]);
}
合并
合并思路很简单:当我们要合并两个集合的时候,首先要判断这两个集合到底是不是两个集合。
- 通过查找方法来寻找两个根节点,看看是不是同一个根节点。如果不是则证明这是两个集合。
- 我们找到这两个根节点之后,要进行一个合并操作,其实就是让一个根节点指向另一个根节点。(本质上是一个认爹的过程)
void Union(int a, int b)
{
int parent_a = FindParent(a);
int parent_b = FindParent(b);
if(parnent_a != parent_b)
parent[parent_a] = parent_b;
}
路径压缩
问题引出
既然并查集是一个树型结构,那么必然存在一种我们在树中最喜闻乐见的一种情况——树退化成链表。
所以并查集也存在这种情况。我们给出的优化方案是很清楚的:把所有节点的双亲节点全部指向根节点,那我们查找的时候就会非常方便。
具体实现思路
找某一个结点x,把这个x到根节点路径上的所有节点的双亲节点全部修改成根节点。(在找的时候顺便就给他改了)
int FindParent(int x)
{
int a = x;//先保存一下x
while(x != parent[x])
x = parent[x];
while(a != parent[a])
{
int b = a;
a = parent[a];
parent[b] = x;
}
return x;
}
//递归
int FindParent(int v)
{
if(v == parent[v])
return v;
else
{
int a = FindParent(parent[v]);
parent[v] = a;
return a;
}
}