引入
先给你一堆数,再多次告诉你哪个数和哪个数在一起。请问:
①最后有几个非连通集合
②给你两个数字,判断是否在一个集合中
这就可以用到并查集
并查集是对树的一种操作,旨在找到某个节点的公共祖先(最老公共祖先)
操作
初始化
想想我们需要什么,我们需要
- 存储每个数的父节点的数组
- 每个数的所在集合个数的数组
- 连通集合个数(最开始为数的个数)
class UnionFind {
int cntOfUnion; //非连通集合个数
int parent[]; //父节点索引数组
int size[]; //各个点的集合大小数组(仅父节点的数值有效)
public int getCntOfUnion() {
return cntOfUnion;
}
public UnionFind(int num) { //输入数据个数
this.parent = new int[num];
this.size = new int[num];
Arrays.fill(size, 1);
//刚开始每个数字都是一个集合,所以size为1
this.cntOfUnion = num;
}
}
正如名字,并查集有两种操作——查询 和 合并
查询通常用来求联通块中各节点的父节点。
合并可统一两点的父节点。
查询
查询思路很好想,就是不断令 x = parent[x], 直到 x == parent[x](即祖先节点)
但这会有很多重复工作。比如我刚才找1,现在找2,那么你就重复搜索了2。此时我们就需要压缩路径,以便后面查找时更便捷。
压缩路径,就是在查找的时候,顺带把经过的节点的祖先直接指向最老祖先,那么后面找最老祖先的时候,就一步到位了。
也就是说
本来是这样:
压缩后变成:
public int find(int x) {
if (parent[x] == x) {
return x;
}
return parent[x] = find(parent[x]);
}
合并
并就是把两个节点合并到一个集合里面(这个集合也是树),每个节点对应一个祖先,最老公共祖先的祖先就是自己,而每个节点在合并前的初始值也是自己
- 单个节点的合并很简单,只需要让父节点数组对应的值为父节点即可。
两个集合呢?
我们需要找到两个集合的“老大”,看看哪个实力硬,也就是集合大小大,让大的吞掉小的,即按秩合并
public boolean unite(int x, int y) { //返回值为是否合并成功
x = find(x);
y = find(y);
if (x == y) {
return false; // 已在同一集合中
}
//接下来让集合小的并向集合大的
if (size[x] < size[y]) {
int tmp = x;
x = y;
y = tmp;
} // 保证 size[x] >= size[y]
parent[y] = x; //让小团体老大归降于大团体老大
--cntOfUnion; //非连通集合数 - 1
return true;
}
判断是否在一个集合中
判断是否在一个集合当中,只需要看他们的祖先节点是否相同即可
public boolean isConnected(int x, int y){ //判断是否在一个集合中
return find(x) == find(y);
}
练习题目
AC代码:
class Solution {
int cntOfUnion; //非连通集合个数
int parent[]; //父节点索引数组
public int getCntOfUnion() {
return cntOfUnion;
}
public void unionfind(int num) {
this.parent = new int[num];
this.cntOfUnion = num;
for (int i = 1; i < num ; ++i) {
parent[i] = i;
}
}
public int find(int x) {
if (parent[x] == x) {
return x;
}
return parent[x] = find(parent[x]);
}
public boolean unite(int x, int y) {
x = find(x);
y = find(y);
if (x == y) {
return false;
}
parent[y] = x;
--cntOfUnion;
return true;
}
public int findCircleNum(int[][] isConnected) {
unionfind(isConnected.length);
for (int i = 0; i < isConnected.length; ++i) {
for (int j = 0; j < isConnected.length; ++j) {
if(isConnected[i][j] == 1){
unite(i, j);
}
}
}
return cntOfUnion;
}
}