並查集
並查集所需要實現的主要有一下幾個功能
1.建立新的集合
2.查找某個元素屬於哪個集合
3.合併兩個集合
我們希望它的算法複雜度達到O(1),那麼具體應該如何實現呢?
方法
UF(int n) //建立並查集
int count() //返回並查集集合數
boolean connected(int a, int b) //測試兩個元素是否在同一個集合內
find(int a) //返回某個元素屬於哪個集合
union(int a, int b)//對兩個點或者這兩個點所屬的元素完成並操作
思路
並查集是一類樹狀結構,非雙向,所以在實現union中應該先實現find(),因爲並的兩個元素可能因爲從屬關係已經相併,或者二者是另外一個大集合的元素。
在實現查操作(find)時,我們維護一個id[]數組用以存儲每個點的label屬性,使用find時將對返回對應點的對應id,同時對於實現並操作時必須滿足維護id[]數組這個條件
成本:在實現unionfind算法時,其成本可視爲訪問id[]數組的次數。
1. Quick-Find
代碼如下:
public int find(int p){ //find的速度會非常的快
return id[p];
}
public void union(int p, int q){ //union需要進行遍歷,相對來說會比較慢
if(find(p) == find(q))
return;
for(int i = 0 ; i < id.length ; i++){
if(id[i] == find(p)) //這裏應該先將p的id計算出來效率會比較快
id[i] = find(q);
}
count--;
}
**分析:**find的速度會非常的快,但是Quick-Find算法無法面對較大的集合,因爲每一次使用Union時都需要對原數組進行一次掃描,其複雜度至少是O(N**2)——>(N+3的單次調用乘上N-1次的最壞情況下調用)
所以我們提出Quick-Union
2.Quick-Union
代碼如下
private int find(int p){
while(p != id[p]){
p = id[p]; //查找根節點
}
return p;
}
void Union(int p , int q){
int pRoot = find(p);
int qRoot = find(q);
if(pRoot == qRoot){
return;
}
id[pRoot] = qRoot;
count --;
}
分析:在這種QuickUnion的情況下,存儲形式的樹形結構被強調了,每個點的id存放的是它父節點的位置,這樣的Union方法會快上很多,同時Connected()方法也需要遍歷到根節點再進行對比。
同時Find方法的時間複雜度爲N,M對Find的複雜度爲o(N*M)—->find總是會遍歷一棵樹,可能這棵樹太大太大
所以我們提出加權的Quick-Union方法,在這個算法中我們總是把小樹連接到大樹上
3.加權Quick-Union
爲了實現加權的QU,我們需要定義幾個新的變量,代碼如下
private int[] size;
private int[] id;
WeightQuickUnionUF(int N){
count = N;
id = new int[N];
size = new int[N];
for(int i = 0 ; i <N ; i ++){
id[i] = i;
}
for(int i = 0 ; i < N ; i++){
size[i] = 1;
}
}
public int find( int p){
while(p != id[p]) p = id[p];
return p;
}
public void union(int p , int q){
int i = find(p);
int j = find(q);
if(i == j) return;
if(size[i] < size[j]){
id[i] = i;
size[j] += size[i];
}
else{
id[j] = i;
size[i] += size[j];
}
count --;
}
複雜度MlgN
爲了實現O(1)的均攤複雜度,可以在每次Union時對路徑進行壓縮,儘量縮短樹的度,這裏提供這個思路,各位水友自行解決吧~
最後轉載個特別好玩的並查集思路
爲了解釋並查集的原理,我將舉一個更有愛的例子。 話說江湖上散落着各式各樣的大俠,有上千個之多。他們沒有什麼正當職業,整天揹着劍在外面走來走去,碰到和自己不是一路人的,就免不了要打一架。但大俠們有一個優點就是講義氣,絕對不打自己的朋友。而且他們信奉“朋友的朋友就是我的朋友”,只要是能通過朋友關係串聯起來的,不管拐了多少個彎,都認爲是自己人。這樣一來,江湖上就形成了一個一個的羣落,通過兩兩之間的朋友關係串聯起來。而不在同一個羣落的人,無論如何都無法通過朋友關係連起來,於是就可以放心往死了打。但是兩個原本互不相識的人,如何判斷是否屬於一個朋友圈呢?我們可以在每個朋友圈內推舉出一個比較有名望的人,作爲該圈子的代表人物,這樣,每個圈子就可以這樣命名“齊達內朋友之隊”“羅納爾多朋友之隊”……兩人只要互相對一下自己的隊長是不是同一個人,就可以確定敵友關係了。但是還有問題啊,大俠們只知道自己直接的朋友是誰,很多人壓根就不認識隊長,要判斷自己的隊長是誰,只能漫無目的的通過朋友的朋友關係問下去:“你是不是隊長?你是不是隊長?”這樣一來,隊長面子上掛不住了,而且效率太低,還有可能陷入無限循環中。於是隊長下令,重新組隊。隊內所有人實行分等級制度,形成樹狀結構,我隊長就是根節點,下面分別是二級隊員、三級隊員。每個人只要記住自己的上級是誰就行了。遇到判斷敵友的時候,只要一層層向上問,直到最高層,就可以在短時間內確定隊長是誰了。由於我們關心的只是兩個人之間是否連通,至於他們是如何連通的,以及每個圈子內部的結構是怎樣的,甚至隊長是誰,並不重要。所以我們可以放任隊長隨意重新組隊,只要不搞錯敵友關係就好了。於是,門派產生了。
來自http://www.cnblogs.com/TonyNeal/p/bingchaji.html
UPDATE!最近研究了下路徑壓縮,然後寫了下面的代碼
package UF;
import edu.princeton.cs.introcs.StdOut;
public class UF_sample_3 {
/**
* now we try to make the Quick-Union tree more short
*
* Modify quick-union to avoid tall trees
*
* Keep track of size of each tree(number of object)
*
* Balance by ** linking root of smaller tree to roof of large trss. **
*
* WEIGHT UNION
*
* Initialized N
*
* Union lgN ↑
*
* Connected lgN
*/
public int[] sz ; //size tree to store the number of son that every roots have
public int[] id ;
public int count ;
public UF_sample_3(int n){
id = new int[n];
sz = new int[n];
count = n ;
for(int i = 0 ; i < n ; i++){
id[i] = i ;
sz[i] = 1 ;
}
}
public int getRoot(int num){
while(num!=id[num]){
id[num] = id[id[num]]; //這裏是關鍵哦!
num = id[num];
}
return num ;
}
public void union(int num1 , int num2){
int pid = getRoot(num1) ;
int qid = getRoot(num2) ;
if(sz[pid] < sz[qid]){id[pid] = qid ; sz[qid] = sz[qid] + sz[pid];}
else{id[qid] = pid ; sz[pid] = sz[qid] + sz[pid];}
}
public boolean connected(int num1 , int num2){
if(getRoot(num1) == getRoot(num2)){
StdOut.println(num1 + ","+ num2 + " they are connected");
return true;
}
else {
StdOut.println(num1 + ","+ num2 + " they are not connected");
return false;
}
}
public void println(){
StringBuilder str = new StringBuilder();
for(int i = 0 ; i < count ; i++){
str.append(id[i] +",");
}
StdOut.println(str.toString());
}
public static void main(String[] args) {
// TODO Auto-generated method stub
UF_sample_3 uf = new UF_sample_3(10);
uf.union(4, 3);
uf.println();
uf.union(3, 8);
uf.println();
uf.union(6, 5);
uf.println();
uf.union(9, 4);
uf.println();
uf.union(2, 1);
uf.println();
uf.union(8, 9);
uf.println();
uf.connected(5, 0);
uf.union(5, 0);
uf.connected(5, 0);
uf.println();
uf.union(7, 2);
uf.println();
uf.union(6, 1);
uf.println();
uf.union(7, 3);
uf.println();
}
}
大家自己試試吧,關鍵的那段代碼我已經標註了~