简介
并查集:是一种判断“远房亲戚”的算法。
打个比方,你身边的某个“朋友”,很有可能就是你父亲的母亲的姑妈的大姨的哥哥的表妹的孙子的女儿的父亲的孙子。如果给定这么一张“家谱”(无向图),如何判断两个顶点是不是“亲戚”呢?用人话说,就是判断一个图中两个点是否联通(两个顶点相互联通则为亲戚)。
并查集是专门用来解决这样的问题的,和搜索不同,并查集在构建图的时候同时就标记出了哪个“人”属于哪个“团伙”(一团伙中的点两两联通)。简单一点来说的话,也就是如若(a)和(b)是男女朋友关系,(b)和(c)是男女朋友关系(。。。。貌似不太恰当),假如(a)和(b)兄弟关系,(b)和(c)是兄弟关系,那么(a)和(c)是兄弟关系。就是这样的逻辑,我们最后要判断的是,让(a)(一般来说是列表的第一位)来代表这整个的集团,谓之一个并查集。
过程分析
它的基本思路就是假若一个图关系当中几个顶点之间互相连通,那么它们具备同一个代表人的关系,也就是它们属于一个集合,一旦属于一个集合,那么可以判定它们存在的关系就是互相之间是亲戚关系:
- 第一步,一般来说我们首先需要为每一个离散成员设置一个身份(代表着,注意开始的时候,他们之间没有关系,所以自己代表自己),也就是计入我们有(n)个成员,那么它们的身份只能是自己,或者自己的一个单值函数,不失一般性:
//并查集初始化,从1开始
int father[maxl];//并查集储存家庭成员
void init()
{
for (int i=1;i<n+1;i++)
father[i] = i;
}
- 第二步,我们知道了每一个人的身份之后呢?显然给出一个人,我们需要知道他属于哪里,也就是寻找他的祖宗(代表者),在树里面递归查询很方便:
//寻找所在集合函数,寻找到根节点即所在的集合
int getFather(int fa1)
{
return father[fa1] == fa1 ? fa1 : getFather(father[fa1]);//递归寻找根节点
}
- 最后需要明白的是,如果我们指定(b)跟(a)是好友,那么(b)的代表者将不是他自己,而是(a),所以更新很重要,想想怎么更新?
father[b] = a
?不能这样做,假如此后又来了一个(c),那么就会出现father[c] = b
.
然而在这个集合里面,b已经不存在了,所以我们需要做的是,让c的代表者(此时还是c)的代表者(c)是b的代表者(a)这样的话,无论后面有多少关系,都不会出现错乱的关系,集合合并也随即完成。
//集合合并
void unionFather()
{
father[getFather(fa1)] = getFather(fa2);
}