并查集
并查集要解决的问题就是集合类型的问题,就是对于集合的并和查。并就是将两个集合合并成一个新的集合,查就是查两个个元素是否在同一个集合中。
因而其提供给用户的接口也应该为两个,一个isSameSet()方法用于查,一个union()方法用于并。
显然,如果是用链表来实现的话,并是一个O(1)的操作,但是查将会是O(n)的操作;而如果用哈希表来实现的话,查是一个O(1)的操作,但是并将会是O(n)的操作。因而,通常情况下使用的是类似链表+哈希表的形式。
原理就是设置头元素来表示一个集合的起始位置,初始情况下,每个元素就是自己集合的头元素。然后集合中所有元素都会有向上指的指针,最终都是会指到集合头元素处,类似于一个个的单链表,这里使用的是一个fatherMap的hash表来实现类似链表的形式,用于存放节点与节点父亲的对应关系。
下面是并查集代码的实现,这种使用HashMap的形式适用于各种数据类型。
public class UnionFindSet_code {
public static class UnionFindSet {
public HashMap<String, String> fatherMap;
public HashMap<String, Integer> sizeMap;
public UnionFindSet(List<String> list) {
fatherMap = new HashMap<>();
sizeMap = new HashMap<>();
//初始化并查集
for (String str : list) {
fatherMap.put(str, str);
sizeMap.put(str, 1);
}
}
private String findHead(String s) {
//先判读是否是并查集中的元素
if (fatherMap.containsKey(s)) {
String temp = s;
//从该元素一直往上走,直到找到父亲为自己的元素,这就是头元素
while (!temp.equals(fatherMap.get(temp))) {
temp = fatherMap.get(temp);
}
return temp;
}
return "";
}
public boolean isSameSet(String a, String b) {
//判断是否是并查集中的元素,不是的话直接返回false
if (fatherMap.containsKey(a) && fatherMap.containsKey(b)) {
//如果两个元素的头为同一个的话,说明它们属于同一个集合
if (findHead(a).equals(findHead(b))) {
return true;
}
}
return false;
}
public void union(String a, String b) {
//判断是否是并查集中的元素
if (fatherMap.containsKey(a) && fatherMap.containsKey(b)) {
//判断是否是同一个集合中的元素,如果不是才进行合并
if (!isSameSet(a, b)) {
String aHead = findHead(a);
String bHead = findHead(b);
String big = sizeMap.get(aHead) > sizeMap.get(bHead) ? aHead : bHead;
String small = aHead == big ? bHead : aHead;
//将元素少的集合合并到元素多的集合中去,也就是将元素少集合的头的父亲设为元素多的集合的头
fatherMap.put(small, big);
//还要将集合元素数量更新
sizeMap.put(big, sizeMap.get(big) + sizeMap.get(small));
sizeMap.remove(small);
}
}
}
}
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("java");
list.add("python");
list.add("c++");
list.add("hadoop");
list.add("hbase");
list.add("mysql");
list.add("redis");
list.add("flink");
list.add("spark");
UnionFindSet unionFindSet = new UnionFindSet(list);
unionFindSet.union("java", "python");
unionFindSet.union("python", "c++");
unionFindSet.union("hadoop", "hbase");
unionFindSet.union("redis", "mysql");
System.out.println(unionFindSet.isSameSet("java", "c++"));
System.out.println(unionFindSet.isSameSet("java", "hadoop"));
System.out.println(unionFindSet.isSameSet("flink", "spark"));
System.out.println(unionFindSet.isSameSet("redis", "mysql"));
}
}
执行程序后,结果所得如下,与图片情况一致。
在做题过程中,总是会遇到对正整数使用并查集的情况,这里就可以不使用HashMap,而是使用数组形式,更加方便一些。代码如下:
public class UnionFindSet_Integer {
public static class UnionFindSet {
public int[] father;
public int[] size;
public UnionFindSet(List<Integer> list, int max) {
father = new int[max + 1];
size = new int[max + 1];
for (Integer i : list) {
father[i] = i;
size[i] = 1;
}
}
private int findHead(int a) {
while (a != father[a]) {
a = father[a];
}
return a;
}
public boolean isSameSet(int a, int b) {
if (findHead(a) == findHead(b)) {
return true;
}
return false;
}
public void union(int a, int b) {
if (!isSameSet(a, b)) {
int aHead = findHead(a);
int bHead = findHead(b);
int big = size[aHead] > size[bHead] ? aHead : bHead;
int small = big == aHead ? bHead : aHead;
father[small] = big;
size[big] = size[big] + size[small];
size[small] = 0;
}
}
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
int max = list.get(list.size() - 1);
UnionFindSet unionFindSet = new UnionFindSet(list, max);
unionFindSet.union(1, 3);
unionFindSet.union(3, 5);
unionFindSet.union(5, 7);
unionFindSet.union(1, 9);
unionFindSet.union(2, 4);
unionFindSet.union(2, 6);
unionFindSet.union(2, 8);
System.out.println(unionFindSet.isSameSet(1, 7));
System.out.println(unionFindSet.isSameSet(1, 6));
System.out.println(unionFindSet.isSameSet(4, 8));
}
}