并查集(Disjoint Set Union)详解与C++实现

可以解决什么问题

常用来解决连通性问题

大白话:就是当我吗需要判断两个元素是否在同一个集合里的时候,我们就要想到用并查集;

并查集主要有两个功能:
1、将两个元素添加到一个集合中;
2、判断两个元素在不在同一个集合;

原理

如何将两个元素添加到同一个集合中呢?

先看看有哪些错误想法:

1、放到同一个数组/set/map中,这样就表述两个元素在同一个集合

那问题来了,如果有成百上千个集合,难道要定义这么多个数组吗,肯定不行

2、定义一个二维数组

如果要判断两个元素是否在同一个集合里,或者添加一个元素到某集合,
只能把二维数组都遍历一遍。

这还是一个粗略的想法,如果沿着这个思路去实现代码,非常负责,因为管理集合还需要很多逻辑

正确解法:

3、将三个元素放在同一个集合,就是将三个元素连通在一起

如何连通呢,只要用一个一维数组表示:
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,B,C在同一个根下,那他们就是同一个集合。

给出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、判断两个节点是否在同一个集合;

更多参考

你可能感兴趣的:(c++,算法,开发语言)