1. 并查集
在一些应用问题中,需要将n个不同的元素划分成一些不相交的集合。开始时,每个元素自成一个 单元素集合,然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一 个元素归属于那个集合的运算。适合于描述这类问题的抽象数据类型称为并查集(union-find set)。
某公司有10人,A地招4人,B地招3人,C地招3人,10个人起先互不相识,每个都是一个独立的小团体。现给这些人进行编号:{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};进行映射。
给以下数组用来存储该小集体,数组中的数字代表:该小集体中具有成员的个数。(-1代表的意义就是有一个人,负号的作用下文会体现)
后来自统一地区的人互相熟悉,建立小团体。
我们如何进行集合合并操作呢?
1.2原理
-
数组的下标对应集合中元素的编号
-
数组中如果为负数,负号代表根,数字代表该集合中元素个数
-
数组中如果为非负数,代表该元素父亲在数组中的下标
以编号0和6为例,我们选0作为父节点,将6编号的集合中的数加给0编号的在集合中的数。再让编号6在集合中的值存放父节点的下标。
综上 我们把三个集合表示完毕
1.2.2 合并的代码
size_t FindRoot(int x)
{
int root=x;
while (_ufs[root] >= 0)
{
root = _ufs[root];
}
// 路径压缩
while (_ufs[x] >= 0)
{
int parent = _ufs[x];
_ufs[x] = root;
x = parent;
}
return root;
}
void Union(int x1, int x2)
{
int root1 = FindRoot(x1);
int root2 = FindRoot(x2);
// 控制数据量小的往大的集合合并
if (abs(_ufs[root1]) < abs(_ufs[root2]))
swap(root1, root2);
if(root1 != root2)
{
_set[root1] += _set[root2];
_set[root2] = root1;
}
}
1.3相关练习
力扣
省份的数量
class Solution {
public:
int findroot(vector<int>&ufs,int n)
{
while(ufs[n]>=0)
{
n=ufs[n];
}
return n;
}
int findCircleNum(vector<vector<int>>& isConnected)
{
vector<int>ufs(isConnected.size(),-1);
for(int i=0;i<isConnected.size();i++)
{
for(int j=0;j<isConnected[i].size();j++)
{
if(isConnected[i][j]==1)
{
int root1=findroot(ufs,i);
int root2=findroot(ufs,j);
if(root1!=root2)
{
ufs[root1]+=ufs[root2];
ufs[root2]=root1;
}
}
}
}
int n=0;
for(auto& e:ufs)
{
if(e<0) n++;
}
return n;
}
};
力扣
等式方程的可满足性
class Solution {
public:
int findroot(int n,vector<int>& ufs)
{
while(ufs[n]>=0)
{
n=ufs[n];
}
return n;
}
bool equationsPossible(vector<string>& equations)
{
vector<int>ufs(26,-1);//每个字母都建立映射关系 a映射0 b映射1
//第一遍 遍历将相同的元素放到同一个集合
for(auto& str:equations)
{
if(str[1]=='=') //两个字母是相等关系
{
int r1=findroot(str[0]-'a',ufs);
int r2=findroot(str[3]-'a',ufs);
if(r1!=r2)
{
ufs[r1]+=ufs[r2];
ufs[r2]=r1;
}
}
}
for(auto& str:equations)
{
if (str[1]=='!') //两个字母是不等关系
{
int r1=findroot(str[0]-'a',ufs);
int r2=findroot(str[3]-'a',ufs);
if(r1==r2)
{
return false;
}
}
}
return true;
}
};