集合的表示
- 集合运算:交、并、补、差、判定一个元素是否属于某一集合
- 并查集:集合 并、查 某元素属于什么集合
- 并查集问题中集合存储如何实现?
- 可以用树结构表示集合,树的每个结点代表一个集合元素
集合运算
- 采用数组存储形式
- 双亲表示法:孩子指向双亲
- 负数表示根结点;非负数表示双亲结点的下标。
- 查找:查找某个元素所在的集合(用根结点表示)
- 并运算:
- 分别找到X1和X2两个元素所在集合树的根结点
- 如果它们不同根,则将其中一个根结点的父结点指针设置成另一个根结点的数组下标
- 合并优化:
- 为了改善合并以后的查找性能,可以采用节点少的集合合并到节点多的集合中
- 也可以根据高度,将树的高度较低的合并到较高的中
- 如果执行了路径压缩,树的高度会变化
- 路径压缩的目的是将从根节点到叶子节点的路径变短,也有多种方式
使用示例
@AllArgsConstructor
@ToString
public class SetType<T> {
/**
* 实体data
*/
T data;
/**
* 父节点索引
*/
int parent;
}
public class UnionSet<T> {
SetType<T>[] setTypes;
/**
* 查找节点
*
* @param x
* @return 返回根节点的索引
*/
public int find(T x) {
int maxSize = setTypes.length;
int i;
// 找到x所在的索引
for (i = 0; i < maxSize && ObjectUtil.notEqual(x, setTypes[i].data); i++) ;
if (i >= maxSize) {
// 未找到X,返回-1
return -1;
}
// 找到x所属集合的根的索引
for (; setTypes[i].parent >= 0; i = setTypes[i].parent) ;
return i;
}
/**
* 查找节点,并进行路径压缩
*
* @param x
* @return 返回根节点的索引
*/
public int findAndCompress(T x) {
int maxSize = setTypes.length;
int i;
// 找到x所在的索引
for (i = 0; i < maxSize && ObjectUtil.notEqual(x, setTypes[i].data); i++) ;
if (i >= maxSize) {
// 未找到X,返回-1
return -1;
}
int j = i;
// 找到x所属集合的根的索引
for (; setTypes[i].parent >= 0; i = setTypes[i].parent) ;
// 路径压缩,将被查找节点的父引用指向根节点
setTypes[j].parent = i;
return i;
}
/**
* 判断两个元素是否属于同一个集合
*
* @param x1
* @param x2
* @return
*/
public boolean isConnected(T x1, T x2) {
return find(x1) == find(x2);
}
/**
* 合并
* <p>
* 基于树的节点个数,小的集合合并到大的集合中
*
* @param x1
* @param x2
*/
public void union(T x1, T x2) {
int root1 = find(x1);
int root2 = find(x2);
if (root1 != root2) {
int x1Parent = setTypes[root1].parent;
int x2Parent = setTypes[root2].parent;
// x1和x2不属于同一个子集时,需要合并
// 小的集合合并到相对大的集合中
if (root1 <= root2) {
setTypes[root2].parent = root1;
setTypes[root1].parent = x1Parent + x2Parent;
} else {
setTypes[root1].parent = root2;
setTypes[root2].parent = x1Parent + x2Parent;
}
}
}
}
public class UnionSetMain {
public static void main(String[] args) {
UnionSet<Integer> unionSet = new UnionSet<>();
unionSet.setTypes = new SetType[10];
unionSet.setTypes[0] = new SetType(1, -4);
unionSet.setTypes[1] = new SetType(2, 0);
unionSet.setTypes[2] = new SetType(3, -3);
unionSet.setTypes[3] = new SetType(4, 0);
unionSet.setTypes[4] = new SetType(5, 2);
unionSet.setTypes[5] = new SetType(6, -3);
unionSet.setTypes[6] = new SetType(7, 0);
unionSet.setTypes[7] = new SetType(8, 2);
unionSet.setTypes[8] = new SetType(9, 5);
unionSet.setTypes[9] = new SetType(10, 5);
for (int i = 0; i < unionSet.setTypes.length; i++) {
Console.log(i, unionSet.setTypes[i]);
}
unionSet.union(2, 3);
Console.log("after union ====================");
for (int i = 0; i < unionSet.setTypes.length; i++) {
Console.log(i, unionSet.setTypes[i]);
}
unionSet.findAndCompress(5);
Console.log("after compress ====================");
for (int i = 0; i < unionSet.setTypes.length; i++) {
Console.log(i, unionSet.setTypes[i]);
}
}
@Test
public void testCompress() {
UnionSet<Integer> unionSet = new UnionSet<>();
unionSet.setTypes = new SetType[10];
unionSet.setTypes[0] = new SetType(0, -10);
unionSet.setTypes[1] = new SetType(1, 0);
unionSet.setTypes[2] = new SetType(2, 1);
unionSet.setTypes[3] = new SetType(3, 2);
unionSet.setTypes[4] = new SetType(4, 3);
unionSet.setTypes[5] = new SetType(5, 4);
unionSet.setTypes[6] = new SetType(6, 5);
unionSet.setTypes[7] = new SetType(7, 6);
unionSet.setTypes[8] = new SetType(8, 7);
unionSet.setTypes[9] = new SetType(9, 8);
unionSet.findAndCompress(5);
unionSet.findAndCompress(5);
Console.log("after compress ====================");
for (int i = 0; i < unionSet.setTypes.length; i++) {
Console.log(i, unionSet.setTypes[i]);
}
}
}
参考资料