一、并查集是什么?
并查集是一种用于处理集合合并与查询的数据结构。它用于解决一些与集合划分相关的问题,例如连通性、等价关系等。当我们需要判断两个元素是否在同一个集合里的时候,我们就要想到用并查集。
二、功能
1.将两个元素添加到一个集合中。
2.判断两个元素在不在同一个集合
三、基本原理
每个集合用一棵树来表示。树根的编号就是整个集合的编号。每个结点存储它的父结点,father[x]表示x的父结点。
四、代码实现
假设我们将三个元素A,B,C (分别是数字)放在同一个集合,其实就是将三个元素连通在一起,如何连通呢。
只需要用一个一维数组来表示,即:father[A] = B,father[B] = C 这样就表述 A 与 B 与 C连通了(有向连通图)。
即
// 将v,u 这条边加入并查集
void add(int u, int v) {
u = find(u); // 寻找u的根
v = find(v); // 寻找v的根
if (u == v) return; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
father[v] = u;
}
//比如说去连通A和B,先去寻找A、B的根,如果两个的根相同,说明在一个集合中,不需要进行操作.
//如果不相同,即father[find(A)] = find(B),也就是合并A、B所在的两个集合
只要 A ,B,C 在同一个根下就是同一个集合。
给出A元素,就可以通过 father[A] = B,father[B] = C,找到根为 C。
给出B元素,就可以通过 father[B] = C,找到根也为为 C,说明 A 和 B 是在同一个集合里。
// 返回x的祖宗节点
int find(int x){
if (father[x] != x) father[x] = find(father[x]);
return father[x];
}
//在实现 find 函数的过程中,我们知道,通过递归的方式,不断获取father数组下标对应的数值,最终找到这个集合的根。
//搜索过程像是一个多叉树中从叶子到根节点的过程。如果这棵多叉树高度很深的话,每次find函数去寻找根的过程就要递归很多次。
//我们的目的只需要知道这些节点在同一个根下就可以,所以我们要进行路径压缩,我们只需要在递归的过程中,让 father[u] 接住 递归函数 find(father[u]) 的返回结果
//因为 find 函数向上寻找根节点,father[u] 表述 u 的父节点,那么让 father[u] 直接获取 find函数 返回的根节点,这样就让节点 u 的父节点 变成根节点。
如果要判断两个元素是否在同一个集合里,可以通过 find函数寻根,如果属于同一个根的话,那么这两个元素就是在同一个集合。
如何表示 C 也在同一个元素里呢? 我们需要 father[C] = C,即C的根也为C,这样就方便表示 A,B,C 都在同一个集合里了。所以我们要进行初始化。
// 并查集初始化
for (int i = 0; i < n; i ++ ) father[i] = i;
五、相关题目
树可以看成是一个连通且 无环 的 无向 图。
给定往一棵 n
个节点 (节点值 1~n
) 的树中添加一条边后的图。添加的边的两个顶点包含在 1
到 n
中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n
的二维数组 edges
,edges[i] = [ai, bi]
表示图中在 ai
和 bi
之间存在一条边。
请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n
个节点的树。如果有多个答案,则返回数组 edges
中最后出现的那个。
解答:
//我们可以从前向后遍历每一条边,边的两个节点如果不在同一个集合,就加入集合。
class Solution {
int[] father;
int find(int x){
if(father[x]!=x) father[x]=find(father[x]);
return father[x];
}
public int[] findRedundantConnection(int[][] edges) {
int n=edges.length;
father=new int[n+1];
//初始化
for(int i=1;i<=n;i++) father[i]=i;
//遍历
for(int i=0;i<=n-1;i++){
int u=find(edges[i][0]);
int v=find(edges[i][1]);
//如果u==v说明有相同的根结点,说明已经连在一起了
if(u==v) return edges[i];
father[v]=u;
}
return new int[]{};
}
}
参考:代码随想录、acwing