概念:并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题(百度百科)
博主理解:
1.并查集可以用来快速查询某一元素在哪个集合中
2.并查集可以快速将两个不在同一个集合的元素,将它们两个所在的集合合并为一个集合
基本介绍:
一般并查集使用数组进行实现
ids = [0, 1, 2, 3, 4, 5, 6, 7]
ids 为一维数组,这个一维数组和其他的数组不一样,
数组下标:用来表示节点编号
数组元素:用来表示父节点编号
起初所有的节点都是一个个独立的集合,每个节点都是集合的根节点
集合的根节点:数组下标=数组元素(节点编号=父节点编号)
1. 查询:查询某一项元素在哪个集合中(实际是找到元素对应节点所在哪个集合的根节点)
ids = [0, 1, 4, 6, 4, 5, 2, 7]
下标:= [0, 1, 2, 3, 4, 5, 6, 7]
上图表示其中某一个集合,集合的根为 4 号节点,
现在想要查询 3 号节点在哪个集合中,需要找到不断地向上查找,找到最后的根 4 号节点
如何完成向上查找,找到根节点的操作呢?
1. ids[3] = 6
2. ids[6] = 2
3. ids[2] = 4
4. ids[4] = 4(找到根节点)
路径压缩:
ids = [0, 1, 4, 6, 4, 5, 2, 7] 变为 ids = [0, 1, 4, 2, 4, 5, 2, 7]
下标:= [0, 1, 2, 3, 4, 5, 6, 7] = [0, 1, 2, 3, 4, 5, 6, 7]
在 3 号节点查找根节点的时候,对路径进行压缩,压缩为上图的样子
那么是如何进行路径压缩的呢?
1. ids[3] = 6 ids[3] = ids[ids[3]] = 2
2. ids[2] = 4 ids[2] = ids[ids[2]] = 4
4. ids[4] = 4(找到根节点)
2.合并:并查集可以快速将两个不相交集合合并为一个集合(实际上是将找到两个集合的根节点,然后两个根节点合并为一个根节点)
把 2 号节点所在集合与 1 号节点所在集合进行合并
步骤:
1.查询 2 号节点的根节点,得到 4 号节点
2.查询 1 号节点的根节点,得到 5 号节点
3.然后根据两颗子树的大小,将小的子树的根节点作为大的子树根节点下的子节点,如果一样大小就随便了(完成合并)
3.代码实现:博主使用的是 Java 语言
public class DisjointSet {
/**
* count 表示集合总个数
*/
int count;
/**
* int[] size 表示根节点代表集合内部有多少个节点
*/
int[] size;
int[] ids;
/**
* 初始化并查集,传入节点个数
*
* @param nodeCount 节点个数
*/
public DisjointSet(int nodeCount) {
this.ids = new int[nodeCount];
this.size = new int[nodeCount];
// 初始时,集合总个数等于节点总个数
this.count = nodeCount;
// 每个节点都是各自集合的根节点,所以每个集合内部节点个数为 1
for (int i = 0; i < nodeCount; i++) {
this.ids[i] = i;
this.size[i] = 1;
}
}
/**
* 根据节点编号查找所在集合的根节点编号
*
* @param node 节点编号
* @return 所在集合的根节点编号
*/
int find(int node) {
while (node != ids[node]) {
// 路径压缩操作
ids[node] = ids[ids[node]];
node = ids[node];
}
return node;
}
/**
* 将节点编号 node1 所在的集合与节点编号 node2 所在的集合进行合并
*
* @param node1 节点编号 1
* @param node2 节点编号 2
*/
void union(int node1, int node2) {
int root1 = find(node1);
int root2 = find(node2);
// 如果两个节点在同一集合,则无需合并
if (root1 == root2) {
return;
}
// 两个集合根节点合并,小的集合根节点作为大的集合根节点的子节点
if (size[root1] < size[root2]) {
ids[root1] = ids[root2];
size[root2] += size[root1];
} else {
ids[root2] = ids[root1];
size[root1] += size[root2];
}
// 每合并一个集合,总集合个数减一
count--;
}
}
4.代码测试:
public static void main(String[] args) {
DisjointSet disjointSet = new DisjointSet(8);
disjointSet.union(0, 1);
disjointSet.union(2, 3);
disjointSet.union(4, 5);
disjointSet.union(1, 4);
System.out.println("集合个数:" + disjointSet.count);
System.out.println("0 号节点和 4 号节点是否在同一集合:" + (disjointSet.find(0) == disjointSet.find(4)));
System.out.println("1 号节点和 2 号节点是否在同一集合:" + (disjointSet.find(1) == disjointSet.find(2)));
}
5.实战演练
拿上述的知识点去解决一个实际问题,例如:LeetCode 547 题,朋友圈问题
问题描述:
6.总结:
算法这个东西太玄妙,得好好学习,我也是刚入门, 希望可以跟大家共同进步!
如果本篇博客有什么不对的地方,请在下方评论区留言,我看到后会更正,
如果有什么不懂的地方,也可以在下方留言,我看到后会一一进行回复。