資料結構09—— 並查集(Union-Find)
阿新 • • 發佈:2018-11-24
一、關於並查集
並查集(Union-Find)是一種樹型的資料結構,常用於處理一些不相交集合(Disjoint Sets)的合併及查詢問題。並查集(Union-Find)從名字可以看出,主要它涉及兩種基本操作:合併和查詢。這說明,初始時並查集中的元素是不相交的,經過一系列的基本操作(Union),最終合併成一個大的集合。
二、並查集的設計和基本實現
1.並查集介面的設計
public interface UF { int getSize(); boolean isConnected(int p, int q); void unionElements(int p, int q); }
2.第一版本的並查集實現:基於陣列
package com.zfy.uf; public class UnionFind1 implements UF { private int[] id; // 我們的第一版Union-Find本質就是一個數組 public UnionFind1(int size) { id = new int[size]; // 初始化, 每一個id[i]指向自己, 沒有合併的元素 for (int i = 0; i < id.length; i++) { id[i] = i; } } @Override public int getSize() { return id.length; } // 查詢元素p所對應的集合編號 // O(1)複雜度 private int find(int p) { if (p < 0 || p >= id.length) throw new IllegalArgumentException("p is out of bound."); return id[p]; } // 檢視元素p和元素q是否所屬一個集合 // O(1)複雜度 @Override public boolean isConnected(int p, int q) { return find(p) == find(q); } // 合併元素p和元素q所屬的集合 // O(n) 複雜度 @Override public void unionElements(int p, int q) { int pID = find(p); int qID = find(q); if (pID == qID) { return; } for (int i = 0; i < id.length; i++) { // 合併過程需要遍歷一遍所有元素, 將兩個元素的所屬集合編號合併 if (id[i] == pID) { id[i] = qID; } } } }
3.第二版的並查集
package com.zfy.uf; public class UnionFind2 implements UF { // 我們的第二版Union-Find, 使用一個數組構建一棵指向父節點的樹 // parent[i]表示第一個元素所指向的父節點 private int[] parent; // 建構函式 public UnionFind2(int size) { parent = new int[size]; // 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合 for (int i = 0; i < size; i++) parent[i] = i; } @Override public int getSize() { return parent.length; } // 查詢過程, 查詢元素p所對應的集合編號 // O(h)複雜度, h為樹的高度 private int find(int p) { if (p < 0 || p >= parent.length) throw new IllegalArgumentException("p is out of bound."); // 不斷去查詢自己的父親節點, 直到到達根節點 // 根節點的特點: parent[p] == p while (p != parent[p]) p = parent[p]; return p; } // 檢視元素p和元素q是否所屬一個集合 // O(h)複雜度, h為樹的高度 @Override public boolean isConnected(int p, int q) { return find(p) == find(q); } // 合併元素p和元素q所屬的集合 // O(h)複雜度, h為樹的高度 @Override public void unionElements(int p, int q) { int pRoot = find(p); int qRoot = find(q); if (pRoot == qRoot) return; parent[pRoot] = qRoot; } }
4. 基於size的優化
package com.zfy.arithmetic.unionfnd;
/**
* Created by vincent on 2018/10/15 上午10:13
*/
public class UnionFind3 implements UF {
private int[] parent; // parent[i]表示第一個元素所指向的父節點
private int[] sz; // sz[i]表示以i為根的集合中元素個數
// 建構函式
public UnionFind3(int size){
parent = new int[size];
sz = new int[size];
// 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
for(int i = 0 ; i < size ; i ++){
parent[i] = i;
sz[i] = 1;
}
}
@Override
public int getSize(){
return parent.length;
}
// 查詢過程, 查詢元素p所對應的集合編號
// O(h)複雜度, h為樹的高度
private int find(int p){
if(p < 0 || p >= parent.length)
throw new IllegalArgumentException("p is out of bound.");
// 不斷去查詢自己的父親節點, 直到到達根節點
// 根節點的特點: parent[p] == p
while( p != parent[p] )
p = parent[p];
return p;
}
// 檢視元素p和元素q是否所屬一個集合
// O(h)複雜度, h為樹的高度
@Override
public boolean isConnected( int p , int q ){
return find(p) == find(q);
}
// 合併元素p和元素q所屬的集合
// O(h)複雜度, h為樹的高度
@Override
public void unionElements(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if(pRoot == qRoot)
return;
// 根據兩個元素所在樹的元素個數不同判斷合併方向
// 將元素個數少的集合合併到元素個數多的集合上
if(sz[pRoot] < sz[qRoot]){
parent[pRoot] = qRoot;
sz[qRoot] += sz[pRoot];
}
else{ // sz[qRoot] <= sz[pRoot]
parent[qRoot] = pRoot;
sz[pRoot] += sz[qRoot];
}
}
}
5.Main測試方法
package com.zfy.arithmetic.unionfnd;
import java.util.Random;
public class Main {
private static double testUF(UF uf, int m){
int size = uf.getSize();
Random random = new Random();
long startTime = System.nanoTime();
for(int i = 0 ; i < m ; i ++){
int a = random.nextInt(size);
int b = random.nextInt(size);
uf.unionElements(a, b);
}
for(int i = 0 ; i < m ; i ++){
int a = random.nextInt(size);
int b = random.nextInt(size);
uf.isConnected(a, b);
}
long endTime = System.nanoTime();
return (endTime - startTime) / 1000000000.0;
}
public static void main(String[] args) {
// UnionFind1 慢於 UnionFind2
// int size = 100000;
// int m = 10000;
// UnionFind2 慢於 UnionFind1, 但UnionFind3最快
int size = 100000;
int m = 100000;
UnionFind1 uf1 = new UnionFind1(size);
System.out.println("UnionFind1 : " + testUF(uf1, m) + " s");
UnionFind2 uf2 = new UnionFind2(size);
System.out.println("UnionFind2 : " + testUF(uf2, m) + " s");
UnionFind3 uf3 = new UnionFind3(size);
System.out.println("UnionFind3 : " + testUF(uf3, m) + " s");
}
}
測試結果:
因為本人太懶,一直沒有畫圖,以後一定會改進!!!!!
結束語:合抱之木,生於毫末;九層之臺,起於累土;千里之行,始於足下。
參考:bobobo老師的玩轉資料結構
版權宣告:尊重博主原創文章,轉載請註明出處 https://blog.csdn.net/zfy163520