定义
并查集,顾名思义,主要功能是合并和查找,主要处理一些集合的合并问题。假设有N
个元素,它们分别属于一些集合,每个元素最多只能属于一个集合,每个集合都有一个“祖先”,这些集合就构成一个并查集。
并查集的表示方式很简单,只需要一个数组v
(数组的大小是元素的个数),对每个元素i
,v[i]
表示它的父节点或祖先节点(如果i
本身是某个集合的祖先节点,则v[i]==i
)。下图的并查集包括4
个集合,v=[0,1,2,2,2,5,5,5,5,5]
。
进一步地,数组counts
表示以每个元素为祖先的集合大小。以上图为例,counts=[1,1,3,0,0,5,0,0,0,0]
。
通常初始化每个元素自成一个集合,所有集合的大小都是1
。
class UnionFind {
private:
vector<int> v;
vector<int> counts;
};
实现
并查集主要有两个方法:查找和合并。
查找
查找并返回元素x
的祖先节点。
int UnionFind::Find(int x) {
int anc = x;
while (v[anc] != anc) {
anc = v[anc];
}
return anc;
}
合并
如果某2
个元素不属于同一集合,且需要合并它们,将小集合合并到大集合里。
x
和y
是它们各自所属集合的祖先节点。
void UnionFind::Merge(int x, int y) {
int anc1 = (counts[x] > counts[y]) ? x : y;
int anc2 = (counts[x] <= counts[y]) ? x : y;
v[anc2] = anc1;
counts[anc1] += counts[anc2];
counts[anc2] = 0;
}
朋友圈问题
题目
题目来源:leetcode 547:朋友圈
班上有N
名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知A
是B
的朋友,B
是C
的朋友,那么我们可以认为A
也是C
的朋友。所谓的朋友圈,是指所有朋友的集合。
给定一个N * N
的矩阵M
,表示班级中学生之间的朋友关系。如果M[i][j] = 1
,表示已知第i
个和j
个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
解题思路
使用并查集的思路:
(1)开始时每个学生自成一个集合。
(2)遍历矩阵M
,根据学生之间的关系及各自的祖先节点,判断是否要合并集合(修改数组v
和counts
)。由于矩阵M
是对称的,所以只需遍历一半。
(3)最后,通过数组v
或counts
统计集合的数量。
代码实现
class UnionFind {
public:
int Find(int x);
void Merge(int x, int y);
int findCircleNum(vector<vector<int>>& M) { // 朋友圈问题
int n = M.size();
if (n == 0) {
return n;
}
counts = vector<int>(n, 1);
v = vector<int>(n);
for (int i = 0; i < n; ++i) {
v[i] = i;
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
if (M[i][j] == 1) {
int ancI = Find(i);
int ancJ = Find(j);
if (ancI != ancJ) {
Merge(ancI, ancJ);
}
}
}
}
int ans = 0;
for (int i = 0; i < n; ++i) {
if (counts[i] != 0) {
++ans;
}
}
return ans;
}
private:
vector<int> v;
vector<int> counts;
};