本人已经打算将这一系列博文做成动画趣味科普的形式来呈现,感兴趣的话可以点这里。
0.并查集是啥?
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
集就是让每个元素构成一个单元素的集合,也就是按一定顺序将属于同一组的元素所在的集合合并。
1.并查集能干啥?
并查集这种数据结构主要用来解决连接问题,什么叫连接问题呢?下面举个栗子,有一个图,里面有很多点,这些点可能和其他点是相连的。如下图所示,那么如果我想知道左上角的点和右下角的点是不是相连通的,你会怎么办?
把图读进内存然后深搜么?虽然可是可以,但深搜解决的是路径问题(即找出起点到终点的详细的路径),我们只需要知道是否相连,可想而知深搜的时间复杂度太高,而且数据量大的时候空间也扛不住。此时并查集就可以派上用场了。
2.实现一个基本的并查集
一个并查集对一组数据应该起码支持两种操作,一个是union(p,q)(也就是将p和q连接起来),另一个是find§(也就是查一下p属于哪个集合)。当然,有了这两种操作之后,就能轻松解决两点是否连接的问题(isConnected(p,q))。
2.1 并查集基本的数据表示
比如我现在有一组10个元素的数据,我们不妨以一个数组来表示。
图中红线上方代表的是数据的索引,红线下方表示的是元素所属集合的编号。也就是说0-4号元素是相连接的(属于同一个集合,集合编号为0),5-9号元素是相连接的(属于同一个集合,集合编号为1)。
2.2 find§
从2.1的数据表示来看,想要得到当前元素所属的集合编号,只要返回元素的值即可。
//查找p所属的集合编号
int find(int p)
{
assert(p >= 0 && p < count);
return id[p];
}
2.3 isConnected(p,q)
有了find操作后,判断两个元素是否属于同一个集合也很容易。
//判断p和q是否属于同一个集合
bool isConnected(int p, int q)
{
return find(p) == find(q);
}
2.4 union(p,q)
union操作是将两个元素所代表的集合合并到同一个集合当中,这个操作其实只要将q所在的集合中所有元素的集合编号换成p的集合编号即可。
//将两个元素的集合并在一起
void unionElements(int p, int q)
{
int pID = find(p);
int qID = find(q);
//p和q本来就是一个集合的,不做处理
if (pID == qID)
{
return;
}
for (int i = 0; i < count; ++i)
{
if (id[i] == pID)
{
id[i] = qID;
}
}
}
2.5 UnionFind代码
class UnionFind
{
public:
UnionFind(int n)
{
count = n;
id = new int[count];
//初始的时候每个元素属于自己独立的集合
for (int i = 0; i < count; ++i)
{
id[i] = i;
}
}
~UnionFind()
{
delete [] id;
}
//查找p所属的集合编号
int find(int p)
{
assert(p >= 0 && p < count);
return id[p];
}
//判断p和q是否属于同一个集合
bool isConnected(int p, int q)
{
return find(p) == find(q);
}
//将两个元素的集合并在一起
void unionElements(int p, int q)
{
int pID = find(p);
int qID = find(q);
//p和q本来就是一个集合的,不做处理
if (pID == qID)
{
return;
}
for (int i = 0; i < count; ++i)
{
if (id[i] == pID)
{
id[i] = qID;
}
}
}
private:
int *id; //集合编号数组
int count; //集合编号数组的大小
};