一、關於查詢和連接數據結構的比較
動態數組 | 鏈表 | 平衡二叉樹 | Set | 並查集 | |
---|---|---|---|---|---|
查詢是否在同一村莊的時間複雜度 | O(n) | O(n) | O(n)---需要遍歷每棵樹的所有結點 | O(n) | O(k) |
合併兩個村莊的時間複雜度 | O(n) | O(n) | O(n)---需要將一棵樹上所有結點都挪到另一棵樹上 | O(n) | O(k) |
二、並查集的設計即實現
1、存儲方式設計
可以用結點數組存儲並查集,並查集其實也是用數組實現的樹形結構,和之前的二叉堆及優先隊列類似。初始化的時候每個結點都看成一個單獨的集合,並且默認父親就是指向自己。
2、怎麼合併結點
每次都先找到待合併兩個結點的根結點,將兩個根節點合併即可。
不過這裏可以分別對合併過程、查詢過程進行相應優化。
合併第一種優化方式可以是將結點少的集合併到結點多的集合下面。
合併第二種就是將高度低的集合併到高度高的集合下面。
查詢第一種優化方式路徑分裂,即將路徑上每一個結點分別指向自己的祖父結點。
查詢第二種優化方式路徑減半,即將路徑上每隔一個結點就指向自己的祖父結點。
3、實現
採用高度優化和路徑減半。
public class MyUnionFind<E> {
private Map<E, Node<E>> nodes;
private static class Node<E> {
Node<E> parent = this; //新建的結點默認就是根結點, 自己指向自己
E element;
int height;
public Node(E element) {
this.element = element;
}
}
public MyUnionFind() {
nodes = new HashMap<>();
}
public void makeSet(E e) {
if (nodes.containsKey(e))
return;
nodes.put(e, new Node<>(e));
}
/**
* 根據某個結點的對象找到根結點的對象
*
* @param e
* @return
*/
public E find(E e) {
Node<E> node = findNode(e);
return node == null ? null : node.element;
}
/**
* 根據某個結點的對象找到根結點
*
* @param e
* @return
*/
private Node<E> findNode(E e) {
Node<E> node = nodes.get(e);
if (node == null)
return null;
//Path Halving優化
while (!Objects.equals(node.element, node.parent.element)) { //根據結點中的對象來比較
node.parent = node.parent.parent;
node = node.parent;
}
return node;
}
public void union(E e1, E e2) {
Node<E> r1 = findNode(e1);
Node<E> r2 = findNode(e2);
if (Objects.equals(r1.element, r2.element))
return;
if (r1.height > r2.height)
r2.parent = r1;
else if (r1.height < r2.height)
r1.parent = r2;
else {
r1.parent = r2;
r2.height++;
}
}
public boolean isSame(E e1, E e2) {
return find(e1) == find(e2);
}
}