參考文章:
https://www.cnblogs.com/gaoquanquan/p/9898624.html java——並查集 UnionFind (強推)
http://www.cnblogs.com/noKing/p/8018609.html#_label0 並查集(Java實現)
https://github.com/liuyubobobo/Play-with-Algorithms
溫馨提示:
如果對於看不懂的代碼,強烈建議拿出紙和筆,來畫一畫圖,跟着代碼走一遍,一遍走不通,過一會再捋一捋。如果實在是捋不順,就找個師傅好好教教。
上面的幾篇文章,有參考的實現代碼,有並查集的原理,我這裏就不廢話,直接上代碼,看原理,建議看視頻,以及優質的博客。
並查集 Union Find 接口定義如下:
public interface UF {
int getSize();
boolean isConnected(int p, int q);
void unionElements(int p, int q);
}
並查集的實現,這裏有6個版本,每一個版本,都有所改進,是逐步的改進,這點 波波老師 的視頻做的非常的好,我就無恥的拿過來了,因爲我也想不出更好的代碼了,至少目前是這樣┭┮﹏┭┮
第一版:
// 我們的第一版Union-Find
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 < size; 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;
}
}
第二版:
// 我們的第二版Union-Find
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;
}
}
第三版:
// 我們的第三版Union-Find
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];
}
}
}
第四版:
// 我們的第四版Union-Find
public class UnionFind4 implements UF {
private int[] rank; // rank[i]表示以i爲根的集合所表示的樹的層數
private int[] parent; // parent[i]表示第i個元素所指向的父節點
// 構造函數
public UnionFind4(int size){
rank = new int[size];
parent = new int[size];
// 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
for( int i = 0 ; i < size ; i ++ ){
parent[i] = i;
rank[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;
// 根據兩個元素所在樹的rank不同判斷合併方向
// 將rank低的集合合併到rank高的集合上
if(rank[pRoot] < rank[qRoot])
parent[pRoot] = qRoot;
else if(rank[qRoot] < rank[pRoot])
parent[qRoot] = pRoot;
else{ // rank[pRoot] == rank[qRoot]
parent[pRoot] = qRoot;
rank[qRoot] += 1; // 此時, 我維護rank的值
}
}
}
第五版:
// 我們的第五版Union-Find
public class UnionFind5 implements UF {
// rank[i]表示以i爲根的集合所表示的樹的層數
// 在後續的代碼中, 我們並不會維護rank的語意, 也就是rank的值在路徑壓縮的過程中, 有可能不在是樹的層數值
// 這也是我們的rank不叫height或者depth的原因, 他只是作爲比較的一個標準
private int[] rank;
private int[] parent; // parent[i]表示第i個元素所指向的父節點
// 構造函數
public UnionFind5(int size){
rank = new int[size];
parent = new int[size];
// 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
for( int i = 0 ; i < size ; i ++ ){
parent[i] = i;
rank[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.");
while( p != parent[p] ){
parent[p] = parent[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;
// 根據兩個元素所在樹的rank不同判斷合併方向
// 將rank低的集合合併到rank高的集合上
if( rank[pRoot] < rank[qRoot] )
parent[pRoot] = qRoot;
else if( rank[qRoot] < rank[pRoot])
parent[qRoot] = pRoot;
else{ // rank[pRoot] == rank[qRoot]
parent[pRoot] = qRoot;
rank[qRoot] += 1; // 此時, 我維護rank的值
}
}
}
第六版:
// 我們的第六版Union-Find
public class UnionFind6 implements UF {
// rank[i]表示以i爲根的集合所表示的樹的層數
// 在後續的代碼中, 我們並不會維護rank的語意, 也就是rank的值在路徑壓縮的過程中, 有可能不在是樹的層數值
// 這也是我們的rank不叫height或者depth的原因, 他只是作爲比較的一個標準
private int[] rank;
private int[] parent; // parent[i]表示第i個元素所指向的父節點
// 構造函數
public UnionFind6(int size){
rank = new int[size];
parent = new int[size];
// 初始化, 每一個parent[i]指向自己, 表示每一個元素自己自成一個集合
for( int i = 0 ; i < size ; i ++ ){
parent[i] = i;
rank[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.");
// path compression 2, 遞歸算法
if(p != parent[p])
parent[p] = find(parent[p]);
return parent[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;
// 根據兩個元素所在樹的rank不同判斷合併方向
// 將rank低的集合合併到rank高的集合上
if( rank[pRoot] < rank[qRoot] )
parent[pRoot] = qRoot;
else if( rank[qRoot] < rank[pRoot])
parent[qRoot] = pRoot;
else{ // rank[pRoot] == rank[qRoot]
parent[pRoot] = qRoot;
rank[qRoot] += 1; // 此時, 我維護rank的值
}
}
}