并查集
并查集是一种森林或树数据结构,常用来处理不相交集合的合并、查询问题。并查集和深度优先搜索、广度优先搜索是图的环的三个查询方法。
-
如上图所示,我们通常将并查集初始化为 n n n个 单节点集合,然后根据题目中提供数据对并查集进行合并,最后可能构成一棵树(连通图)或者一个森林(非连通图)。
-
采用并查集处理的问题一般都有如下特点:
- 数据量极大,用其他数据结构往往空间复杂度难以接受。
- 使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果。
-
在处理图的连通性问题中也可以采用并查集问题,一般多用于求数量问题。
需要注意的是,并查集并不能像深度优先(DFS)和广度优先(BFS)那样得到图的遍历路径。
例题如下:
Alice 和 Bob 共有一个无向图,其中包含 n 个节点和 3 种类型的边:
类型 1:只能由 Alice 遍历。
类型 2:只能由 Bob 遍历。
类型 3:Alice 和 Bob 都可以遍历。
给你一个数组 edges ,其中 edges[i] = [typei, ui, vi] 表示节点 ui 和 vi 之间存在类型为 typei 的双向边。请你在保证图仍能够被 Alice和 Bob 完全遍历的前提下,找出可以删除的最大边数。如果从任何节点开始,Alice 和 Bob 都可以到达所有其他节点,则认为图是可以完全遍历的。返回可以删除的最大边数,如果 Alice 和 Bob 无法完全遍历图,则返回 -1 。
来源:力扣(LeetCode)
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
- 根据题中描述,我们可以了解到,当在两点之间有type3和任意一种边时,我们可以将非type3的边删除,另一种情况是,当存在环时,我们可以删除一条边。
- 保证两人都能都完全遍历,我们可以这个条件看做两人的并查集最后都是一棵树,即只有一个连通分量的情况,如上图所示。
- 值的注意的是,这道题中,我们必须先将公共边加入二者的并查集中并处理。
- 我们在计算可删除边的数量时,首先判断当前边是否已经在连通分量中,如果已经存在,我们可以删掉这个边。因为公共边的删除会影响两个人的连通性,所以如果我们需要先单独处理公共边,之后删除二者的单独边不会对对方产生影响。同时,我们需要删除尽可能多的边,二人达到一个结点需要两条独占边或者一条公共边,在数量上考虑我们也需要先处理公共边。
public int maxNumEdgesToRemove(int n, int[][] edges) {
Union djsa = new Union(n);
Union djsb = new Union(n);
int ans = 0;
for(int[] item : edges){
//common
if(item[0] == 3){
//when edge is not in two union , djsa and djsb add the edge, otherwise, ans++
if(!djsa.uniont(item[1]-1,item[2]-1)){
//edge is in djsa and djsb
ans++;
}else{
//edge is not in djsa and djsb
djsb.uniont(item[1]-1,item[2]-1);
}
}
}
for(int[] item : edges){
//type1
if(item[0] == 1){
if(!djsa.uniont(item[1]-1,item[2]-1)){
ans++;
}
continue;
}
//type2
if(item[0] == 2){
//surplus edge should be removed
if(!djsb.uniont(item[1]-1,item[2]-1)){
//the edge has in the union
ans++;
}
continue;
}
}
if(djsa.count!=1 || djsb.count != 1){
//one person can't arrive some point
return -1;
}
return ans;
}
并查集模板
public class Union{
/**
*并查集模板
**/
//根节点集
private int[] parent;
//连通分量中结点数
private int[] size;
//连通分量数目
int count;
public Union(int n){
size = new int[n];
Arrays.fill(size,1);
parent = new int[n];
for(int i=0; i < n; i++){
this.parent[i] = i;
}
this.count = n;
}
//找到根节点
public int find(int index){
return index == this.parent[index] ? index : (parent[index] = find(this.parent[index]));
}
//将两个不在同一个连通分量的点放到一起
public boolean uniont(int index1,int index2){
int root1 = find(index1);
int root2 = find(index2);
if(root1 == root2){
//有相同的根节点,返回false
return false;
}
this.parent[root1] = root2;
this.size[root2] += size[root1];
this.count--;
return true;
}
}