1.作用
并查集,顾名思义“合并”和“查找”,是一种树形的数据结构,合并一些不相交的集合以及查找根节点等问题,可以用来解决连通性问题。
下面用一个例子“诠释”这个合并和查找的问题:
假设某市里有10个小镇,两个小镇之间的连线表示连接该两个小镇的路,比如小镇1和小镇2直接相连,小镇1和小镇3间接相连。
“查”:给你两个小镇,让你判断两个小镇是否相连(直接相连或者间接相连),则查找每个小镇的“根节点”是否相同,如果“根节点”为同一个镇的话则说明这两个镇是相连的,否则不相连。
“并”:问这些小镇是否已经全连通,如果已经全连通了则不需要再修路了,如果没有全连通则需要修一条路将两个不相连的小镇集合连起来,也就是合并。
设小镇集合为x = 1,2,3,4,5,6,7,8,9,10,他们对应的前驱用数组表示pre = [1,1,1,2,3,6,6,6,6,9]:
2.查
由以上给出的信息查找“根节点”
private int find(int x){
while(pre[x] != x){
x = pre[x];
}
return x
}
由于“并”和“查”的结果与元素间的连通路线无关,所以把所有子节点直接连接到对应的根节点,以减少计算量
private int find(int x){
if(pre[x] != x)
pre[x] = find(pre[x]);
return pre[x];//如果前驱等于它自己说明自己就是根节点
}
3.并
查出“根节点”之后通过判断有几个根节点,就这点要修多少条路连通,接下来就是合并:
矮的树并到高的树下面去,若两棵树树高相等则选择其中一个作为最后的根节点,为了判断树高,运用加权标记法,sz[i]表示每个节点的树高,合并之后的树如下;
public void union(int p, int q){
int pRoot = find(p);//查找p的根节点
int qRoot = find(q);//查找q的根节点
if(pRoot == qRoot)//如果相等则说明p和q同根节点
return;
if(sz[pRoot] > sz[qRoot]){
pre[qRoot] = pRoot;//如果p的高度大于q的,则令q的上级为p
}
else{
if(sz[qRoot] == sz[pRoot])//如果高度相同,则令q的高度加1
sz[qRoot]++;
pre[pRoot] = qRoot;//则令p的上级为q
}
}
创建并查集
//加权并查集
class WeightedUnionFind{
private int[] pre;//每个节点的前驱
private int[] sz;//每个节点的权值
private int count;//一共有多少个元素
public WeightedUnionFind(int count){//对录入的n个节点进行初始化
this.count = count;//并查集所包含的元素
pre = new int[count];
sz = new int[count];
for(int i=0; i<count; i++){
pre[i] = i;//初始化每个节点的上级都是他自己
sz[i] = 1;//每个节点构成的树高度是1
}
}
//两节点是否相连
public boolean isConnetion(int p, int q){
return find(p) == find(q);
}
//合并两个节点所在的树
public void union(int p, int q){
int pRoot = find(p);//查找p的根节点
int qRoot = find(q);//查找q的根节点
if(pRoot == qRoot)//如果相等则说明p和q同根节点
return;
if(sz[pRoot] > sz[qRoot]){
pre[qRoot] = pRoot;//如果p的高度大于q的,则令q的上级为p
}
else{
if(sz[qRoot] == sz[pRoot])//如果高度相同,则令q的高度加1
sz[qRoot]++;
pre[pRoot] = qRoot;//则令p的上级为q
}
}
//查找根节点
private int find(int p){
while(p != pre[p]){
pre[p] = pre[pre[p]];//压缩查找路线
p = pre[p];
}
return p;
}
例题1(LeetCode 684)
class Solution {
public int[] findRedundantConnection(int[][] edges) {
int len = edges.length;
int[] parent = new int[len+1];
for(int i=1; i<len+1; i++){
parent[i] = i;
}
for(int i=0; i<len; i++){
int[] edge = edges[i];
int node1 = edge[0], node2 = edge[1];//node1和node2之间是有线相连的
if(find(parent, node1) != find(parent, node2)){//如果两个数的前驱不相等的话,就合并
union(parent, node1, node2);
}
else{
return edge;//如果两个点的前驱相等,则他们之间就不应该再相连,不然会形成闭环,所以要返回这对数
}
}
return new int[0];
}
public void union(int[] parent, int node1, int node2){
parent[find(parent, node1)] = find(parent, node2);
}
public int find(int[] parent, int node){
if(parent[node] != node)
parent[node] = find(parent, parent[node]);
return parent[node];
}
}
例题2(LeetCode 547)
class Solution {
public int findCircleNum(int[][] isConnected) {
WeightedUnionFind wuf = new WeightedUnionFind(isConnected.length);
for(int i=0; i < isConnected.length; i++){
for(int j=0; j<isConnected.length; j++){
if(isConnected[i][j] == 1){
wuf.union(i,j);
}
}
}
return wuf.getCount();
}
}
class WeightedUnionFind{
private int[] pre;//每个节点的前驱
private int[] sz;//每个节点的权值
private int count;//一共有多少个元素
public WeightedUnionFind(int count){//对录入的n个节点进行初始化
this.count = count;//并查集所包含的元素
pre = new int[count];
sz = new int[count];
for(int i=0; i<count; i++){
pre[i] = i;//初始化每个节点的上级都是他自己
sz[i] = 1;//每个节点构成的树高度是1
}
}
//两节点是否相连
public boolean isConnetion(int p, int q){
return find(p) == find(q);
}
//合并两个节点所在的树
public void union(int p, int q){
int pRoot = find(p);//查找p的根节点
int qRoot = find(q);//查找q的根节点
if(pRoot == qRoot)//如果相等则说明p和q同根节点
return;
if(sz[pRoot] > sz[qRoot]){
pre[qRoot] = pRoot;//如果p的高度大于q的,则令q的上级为p
}
else{
if(sz[qRoot] == sz[pRoot])//如果高度相同,则令q的高度加1
sz[qRoot]++;
pre[pRoot] = qRoot;//则令p的上级为q
}
count--;
}
//查找根节点
private int find(int p){
while(p != pre[p]){
pre[p] = pre[pre[p]];//压缩查找路线
p = pre[p];
}
return p;
}