并查集定义
并查集(Union合并,Find查找,Set集合)是一种维护集合的数据结构,并查集支持一下两个操作:
- 合并:合并两个集合
- 查找:判断两个原色是否在用一个集合(寻找根结点)
并查集通过一个一维数组 int father[N] 实现。其中father[i] 表示节点 i 的父结点。如果,father[i] == i ,则说明结点 i 为根结点;对于同一个结合,只有一个根结点,将其作为一个集合的标识。
以上图为例,图中分为了两个集合,(1,2,5)为一个集合,(3,4)为一另一个结合,有两个根节结点。
father[1]=1; //1的父结点为1
father[2]=1; //2的父结点为1
father[3]=3;
father[4]=3;
father[5]=1;
并查集的基本工作
1、初始化
一开始,每一个结点都是独立的一个集合,令father[i]=i。
void init(int n)
{
for(int i=1;i<=n;i++){
father[i]=i;
isroot[i]=false;
}
}
2、查找
规定同一个集合中只存在一个根结点,因此查找操作就是对给定的结点寻找其根结点的过程,可以通过递推或者递归实现。
- 递推
int find_father(int x) //返回结点x所在集合的根结点
{
while(x!=father[x]){
x=father[x];
}
return x;
}
- 递归
int find_father(int x) //返回结点x所在集合的根结点
{
if(x==father[x]) return x;
else return find_father(father[x]);
}
3、合并
合并是指将两个集合合并成一个集合。题目中一般给出两个元素a和b,对其进行合并操作;首先需要判断a和b是否在同一个集合中,如果在则不需要再进行合并了;如果不在, 则进行合并,将其中一个集合的根节点的父亲指向另一个集合的根结点。
void Union(int a,int b)
{
int faa=find_father(a);
int fab=find_father(b);
if(faa!=fab) father[faa]=fab;
}
路径压缩
上诉的查找函数是没有经过优化的。观察下面的图片。
在上图中,为了查找集合的根结点,我们可以对其进行变化。这样相当于把当前查询结点的路径上的所有结点的父亲都指向根结点,查找时就不需要回溯了,得到以下优化后 的路径压缩代码。
- 递推
int find_father(int x)
{
int a=x;
while(x!=father[x]){
x=father[x];
}
while(a!=father[a]){ //x存放的是根结点
int z=a; //a值被覆盖
a=father[a]; //回溯到父结点
father[z]=x;
}
return x
}
- 递归
int find_father(int x)
{
if(x==father[x]) return x;
else{
int f=find_father(father[x]);
father[x]=f;
return f;
}
}
题目-LeetCode1319
该题为并查集类,答题过程与并查集解决的模板差别不大。以下为原题链接。
https://leetcode-cn.com/problems/number-of-operations-to-make-network-connected/
题目需要求出最少需要移动的数目,我们在通过并查集的相关操作,将计算机分成cnt个集合以后,分析可知,每个集合之间只需要添加一条线缆即可连接,即最少移动的线缆条数为cnt-1条。同时我们也要注意,线缆的条数至少为n-1条,才可以将n个计算机连接起来。
具体代码如下所示。
class Solution {
public:
int father[100005];
bool isroot[100005];
void init(int n){
for(int i=0;i<n;i++){
father[i]=i;
isroot[i]=false;
}
}
int find_father(int x){
if(x==father[x]) return x;
else{
int f=find_father(father[x]);
father[x]=f;
return f;
}
}
void Union(int a,int b){
int fa=find_father(a);
int fb=find_father(b);
if(fa!=fb) father[fa]=fb;
}
int makeConnected(int n, vector<vector<int>>& connections) {
int len=connections.size();
if(len+1<n) return -1; //分析可知,至少需要n-1条线缆
int cnt=0;
init(n);
for(int i=0;i<len;i++){
Union(connections[i][0],connections[i][1]);
}
for(int i=0;i<n;i++) isroot[find_father(i)]=true;
for(int i=0;i<n;i++) if(isroot[i]) cnt++;
return cnt-1;
}
};