并查集原理
在一些有N个元素的集合应用问题中,需要将N个不同的元素划分成一些不相交的集合。开始时,每一个元素自成一个单元素集合,然后按照一定的规律将归于同一组元素的集合合并。其间要反复查找一个元素在哪个集合中。适用于描述这一类问题的抽象数据类型称为并查集(Union find set)。
实际上,并查集的存储是一个森林,每一个集合是一个树。
观察数组融合规律,得出以下结论:
- 数组的下标对应集合中元素的编号
- 数组中如果为负数,负数代表根,数字代表该集合中的元素个数
- 数组中如果为负数,代表元素双亲在数组中的下标
通过以上例子可知,并查集一般可以解决以下问题:
- 查找元素属于那个集合
数组存放的是父节点,一直往上可以找到根 - 查看两个元素是否属于同一个集合
查看两个元素的根节点是否相同,相同则在同一个集合 - 将两个集合归并成一个集合
- 集合的个数
遍历数组,查看元素为负数的个数即为集合的个数
并查集实现
class UnionFindSet {
public :
UnionFindSet(int n) {
_v.resize(n,-1);
}
int FindRoot(int x) {
while (_v[x] >= 0) {
x = _v[x];
}
return x;
}
bool Union(int x1,int x2) {
int root1 = FindRoot(x1);
int root2 = FindRoot(x2);
// x1 和 x2 的根相同,本就在同一个集合中
if (root1 == root2)
return false;
_v[root1] += _v[root2];
_v[root2] = root1;
return true;
}
private:
vector<int> _v;
};
应用
class Solution {
public:
class UnionFindSet {
public :
UnionFindSet(int n) {
_v.resize(n,-1);
}
int FindRoot(int x) {
while (_v[x] >= 0) {
x = _v[x];
}
return x;
}
bool Union(int x1,int x2) {
int root1 = FindRoot(x1);
int root2 = FindRoot(x2);
// x1 和 x2 的根相同,本就在同一个集合中
if (root1 == root2)
return false;
_v[root1] += _v[root2];
_v[root2] = root1;
return true;
}
int Size() {
int n = 0;
for(int e : _v) {
if(e < 0)
++n;
}
return n;
}
private:
vector<int> _v;
};
int findCircleNum(vector<vector<int>>& M) {
UnionFindSet ufs(M.size());
for(int i = 0; i < M.size(); ++i) {
for(int j = 0; j < M[i].size(); ++j) {
if(M[i][j] == 1)
ufs.Union(i,j);
}
}
return ufs.Size();
}
};
class UnionFindSet {
public :
UnionFindSet(int n) {
_v.resize(n,-1);
}
int FindRoot(int x) {
while (_v[x] >= 0) {
x = _v[x];
}
return x;
}
bool Union(int x1,int x2) {
int root1 = FindRoot(x1);
int root2 = FindRoot(x2);
// x1 和 x2 的根相同,本就在同一个集合中
if (root1 == root2)
return false;
_v[root1] += _v[root2];
_v[root2] = root1;
return true;
}
private:
vector<int> _v;
};
class Solution {
public:
bool equationsPossible(vector<string>& equations) {
// 无法确定大小,但是小写字母只有26个
UnionFindSet ufs(26);
// 先遍历一遍,将相等的,先放进同一个集合
for(auto& str : equations) {
if(str[1] == '=') {
// 数组大小只有 26 ,所以存相对位置,减去字符 a
ufs.Union(str[0] - 'a',str[3] - 'a');
}
}
for(auto& str : equations) {
// 如果在同一个集合,但是两个字母又不相等,就返回 false
if(str[1] == '!') {
if(ufs.FindRoot(str[0] - 'a')
== ufs.FindRoot(str[3] - 'a'))
return false;
}
}
// 没有返回 false 说明都相等,没有在一个集合但又不相等的
return true;
}
};