常用来解决连通性问题
大白话:就是当我吗需要判断两个元素是否在同一个集合里的时候,我们就要想到用并查集;
并查集主要有两个功能:
1、将两个元素添加到一个集合中;
2、判断两个元素在不在同一个集合;
如何将两个元素添加到同一个集合中呢?
那问题来了,如果有成百上千个集合,难道要定义这么多个数组吗,肯定不行
如果要判断两个元素是否在同一个集合里,或者添加一个元素到某集合,
只能把二维数组都遍历一遍。
这还是一个粗略的想法,如果沿着这个思路去实现代码,非常负责,因为管理集合还需要很多逻辑
如何连通呢,只要用一个一维数组表示:
father[A]=B,father[B]=C
这样就表述A与B与C连通了(有向连通图)
代码如下:
//将v,u这条边加入并查集
void join(int i,int v){
u=find(u);//寻找u的根
v=find(v);//寻找v的根
if(u==v) return;//如果发现根相同,则说明在同一个集合,不用两个节点相连,直接返回
father[v]=u;
}
这样我就可以知道A连通B,因为A是索引下标,根据father[A]
的数值,就知道A连通B。那怎么知道B连通A呢?
我们的目的是判断这三个元素是否在同一个集合里,就知道A连通B就已经足够了。
给出A元素,就可以通过 father[A] = B,father[B] = C,找到根为 C。
`给出B元素,就可以通过 father[B] = C,找到根也为为 C,
说明 A 和 B 是在同一个集合里
find代码如下:
//并查集里的寻根过程
int find(int u){
if(u==father[u]) return u;
else return find(father[u]);
}
如何表示 C 也在同一个元素里呢? 我们需要 father[C] = C
,即C的根也为C,这样就方便表示 A,B,C 都在同一个集合里了。
所以father数组初始化的时候要 father[i] = i
,默认自己指向自己。
代码如下:
void init(){
for(int i=0;i
最后我们如何判断两个元素是否在同一个集合里,如果通过 find函数 找到 两个元素属于同一个根的话,那么这两个元素就是同一个集合,代码如下:
bool isSame(int u,int v){
u=find(u);
v=find(v);
return u==v;
}
按照以上思路,会发现find就是不断获取father数组对应下标的过程。如果树很高,那效率就很低。
![[Pasted image 20240406193000.png]]
但我们只需要知道这些节点在同一个根下就可以,所以可以简化为
![[Pasted image 20240406193020.png]]
代码如下,注意看注释,路径压缩就一行代码:
// 并查集里寻根的过程
int find(int u) {
if (u == father[u]) return u;
else return father[u] = find(father[u]); // 路径压缩
}
以上代码在C++中,可以用三元表达式来精简一下,代码如下:
int find(int u) {
return u == father[u] ? u : father[u] = find(father[u]);
}
整体c++模版如下:
int n=1005;//根据题目节点数量而定
vector father=vector(n,0);
//并查集初始化
void init(){
for(int i=0;iu这条边加入并查集
void join(int u,int v){
u=find(u);
v=find(v);
if(u==v) return;
father[v]=u;
}
通过模版,我们可以知道,并查集主要有三个功能:
1、寻找根节点;
2、将两个节点接入到同一个集合;
3、判断两个节点是否在同一个集合;
更多参考