并查集
when ? how ? what ? why?
what
什么是并查集?
how
并查集问题中集合存储如何实现?
并查集的操作
什么是并查集?
集合的合并、查询某元素属于什么集合。
并查集问题中集合存储如何实现?
可以用树结构表示集合,树的每个结点代表一个集合元素
集合
S1={1,2,3,4},S2={5,6,7},S3={8,9,10}
使用双亲表示法:孩子指向双亲
并查集的操作
使用数组来存储,数组对应的下标来表示元素的值,数组中的值表示父亲结点。
0 下标不存放元素,-1 表示为根。大于 0 的值表示父亲结点元素。
初始化
将数组中的所有元素赋值为 -1。
public static void init(int[] setData) {
for (int i = 1; i < setData.length; i++) {
setData[i] = -1;
}
}
查找
查找某元素对应集合的根结点的值
public static int find(int[] setData, int x) {
for (; setData[x] > 0; x = setData[x]) {
}
return x;
}
合并
先查找两个元素对应集合根结点是否相同,如果相同就不用合并。
public static void union(int[] setData,int numA,int numB){
int rootA=find(setData,numA);
int rootB=find(setData,numB);
if(rootA!=rootB){
setData[rootB]=rootA;
}
}
问题?
将 1,2,3,4,5 合并后
那么查找的时间复杂度 O(N)
通过按秩合并或路径压缩来优化。
按秩合并
按规模或按高度进行合并的算法,就统称按秩合并
1.将矮树合并到高树上
将原本根结点 -1 对应的值改为 - 树高
setData[root] = - 树高
public static void union(int[] setData,int numA,int numB){
int rootA=find(setData,numA);
int rootB=find(setData,numB);
if(setData[rootA]<setData[rootB]){
setData[rootB]=rootA;
}else{
if(setData[rootA]==setData[rootB]){
setData[rootB]--;
}
setData[rootA]=rootB;
}
}
2.将规模小的树合并到规模大的树
将原本根结点 -1 对应的值改为 - 树的规模
setData[root] = - 树的结点数
public static void union(int[] setData, int numA, int numB) {
int rootA = find(setData, numA);
int rootB = find(setData, numB);
if (rootA != rootB) {
if (setData[rootA] < setData[rootB]) {
setData[rootA] = setData[rootA] + setData[rootB];
setData[rootB] = rootA;
} else {
setData[rootB] = setData[rootB] + setData[rootA];
setData[rootA] = rootB;
}
}
}
路径压缩
public static int find(int[] setData,int x){
if(setData[x]<0){
//找到根结点
return x;
}else {
return setData[x] = find(setData, setData[x]);
}
}
调用 find(setData,5) 后 树结构如下图,当第二次查询结点 5 时只需要2次比较就可以,这样大大提高效率。
总结
参考
数据结构 陈越 何钦铭
主要是用树表示集合,使用双亲表示法,操作集合的合并、查询元素所在的集合,以及通过按秩合并来优化集合的合并、路径压缩来优化查找。
有什么问题欢迎指出来,十分感谢!
才学疏浅,有什么问题请大家指出来。十分感谢!