1. 並查集
1.1 並查集的概念
並查集也是一種數據結構,主要有兩個操作,集合的合併,判斷兩個元素是否屬於同一個集合,是一種邏輯結構。
1.2 並查集的實現思路
並查集的底層實現有多重方式,可以使用數組、List、樹等等,選擇的依據是哪種實現方式可以讓操作的複雜度小。可以使用一種特殊的樹來實現並查集如圖,每一個結點都有一個父結點,最後一個結點的父節點指向自己,也是這個集合的代表結點,判斷兩個元素是否屬於同一個集合,就是判斷這兩個元素所在集合的代表結點是否相等,合併兩個集合,讓結點數少的集合,掛載結點數多的代表結點的下面。
具體實現時,可以使用Map,使操作更簡單。
1.3 代碼
public class Code_09_UnionFind {
public static class Node{
// whatever you like
}
public static class DisjointSets{
//定義兩個Map,fatherMap<Node, fatherNode>, 存儲結點和他的父節點,sizeMap<Node, Integer>
//存儲結點和以這個結點爲父節點的結點個數,並且,只有當這個結點是代表結點的時候,sizeMap中的
//value值纔有意義
public Map<Node, Node> fatherMap;
public Map<Node, Integer> sizeMap;
public DisjointSets(List<Node> list){
fatherMap = new HashMap<>();
sizeMap = new HashMap<>();
makeSets(list);
}
//初始化並查集,使用並查集的條件是,必須要知道所有元素
//開始的時候,每個結點的父節點都是自己。
public void makeSets(List<Node> list){
fatherMap.clear();
sizeMap.clear();
for(Node node : list){
fatherMap.put(node, node);
sizeMap.put(node, 1);
}
}
//根據一個元素,查找這個元素所在的集合的代表結點,並且,將這個集合壓縮,就是從這個結點
//到代表結點之間的所有節點的父節點,都指向代表結點
public Node findFather(Node node){
Node father = fatherMap.get(node);
if(father != node){
//這裏使用的是遞歸,如果node的父節點不是father,就找父節點的父節點,直到找到
//代表結點,node是一直在變的
father = findFather(father);
}
//找到代表結點之後,將每個結點的父節點設爲代表結點
fatherMap.put(node, father);
//返回father
return father;
}
//判斷兩個元素是否屬於同一個集合
public boolean isSameSet(Node node1, Node node2){
return findFather(node1) == findFather(node2);
}
//合併兩個集合
public void union(Node a, Node b){
if(a == null || b == null) return;
Node aFather = findFather(a);
Node bFather = findFather(b);
if(aFather != bFather){
int aFatherSize = sizeMap.get(aFather);
int bFatherSize = sizeMap.get(bFather);
if( aFatherSize <= bFatherSize){
//如果a所在集合結點數少於b的,就將a的父節點設爲b的父節點,a掛載b後頭
fatherMap.put(aFather, bFather);
//修改個數
sizeMap.put(bFather, aFatherSize + bFatherSize);
}else{
fatherMap.put(bFather, aFather);
sizeMap.put(aFather, aFatherSize + bFatherSize);
}
}
}
}
}
時間複雜度:假設有n個元素,如果查詢的次數(查找代表結點)加上合併的次數的數量級達到或者超過了O(n),那麼,單次查詢或者單次合併,平均複雜度爲O(1)。證明過程很複雜,略。
如有不足之處,歡迎指正,謝謝!