給定乙個沒有重複值的整形陣列arr,初始時認為arr中每乙個數各自都是乙個單獨的集合。請設計一種叫unionfind的結構,並提供以下兩個操作。
boolean issameset(int a, int b): 查詢a和b這兩個數是否屬於乙個集合
void union(int a, int b): 把a所在的集合與b所在的集合合併在一起,原本兩個集合各自的元素以後都算作同乙個集合
[要求]
如果呼叫issameset和union的總次數逼近或超過o(n),請做到單次呼叫issameset或union方法的平均時間複雜度為o(1)
按照樹形結構給每個數設定自己的父親 可以用map或者陣列來表示
比如, , , , => [1, 1, 3, 4, 5, 6],陣列的下標表示數 值表示對應的父節點
如果乙個數沒有父節點,特別地設定該數的父節點為自身
陣列用parent來表示
要查詢乙個數所屬的集合(通過祖先來表示) 可以遞迴向上找到祖先結點(父節點等於自身的)
public
intfind
(int x)
return x;
}
上面這種方法就是單純的樹結構,但是這種方法在查詢的時候需要逐層向上查詢,但是實際上想知道數x的祖先是多少並不需要知道x的父親是多少,類似給葉子節點加上乙個指向根節點的指標的思想,直接將x的父親設定為x的祖先
public
intfind
(x)return parent[x]
;}
合併兩個數所在的集合不需要考慮合併的方法,只要合併後兩個數能夠找到同乙個祖先即可,再根據路徑壓縮的思想 直接把其中乙個的祖先設定為另乙個祖先的兒子
public
void
union
(int x,
int y)
parent[f1]
= f2;
}
上面的合併是兩者間隨意合併,現有樹t1和t2有以下兩種情況,合併後的樹為t3
t1合併到t2
t2合併到t1
假設t1的高度比t2的高度高
那麼情況1相比於情況2合併後的樹t3的高度會增加,也就會增大下次查詢的路徑長度
因此可以根據樹的高度 選擇較小的合併到較大的樹上
因此在初始化的時候引入新的陣列rank用來表示每棵樹的高度,這種方法也稱按秩合併
public
void
union
(int x,
int y)
int r1 = rank[f1]
;int r2 = rank[f2];if
(r1 > r2)
else
if(r1 == r2)
else
}
也可以換乙個標準,按照樹的結點數作為評估標準,此時rank表示每棵樹的結點個數
public
void
union
(int x,
int y)
int r1 = rank[f1]
;int r2 = rank[f2];if
(r1 > r2)
else
}
使用路徑壓縮和啟發式合併之後,並查集的每個操作平均時間為o(α
(n))
o(\alpha(n))
o(α(n)
),只說下結論可以看成常數時間
兩個陣列,複雜度o(n
)o(n)
o(n)
並查集 - oi wiki
並查集 並查集
本文參考了 挑戰程式設計競賽 和jennica的github題解 陣列版 int parent max n int rank max n void init int n int find int x else void union int x,int y else 結構體版 struct node ...
java實現並查集
並查集是根據這篇博文學習的,寫得很不錯 下面是實現 public class unionfindset count n 合併p,q所在集合 param p param q public void union int p,int q if size rootp size rootq else coun...
C 實現並查集
將n個不同的元素分成一組不相交的集合。開始時,每個元素就是乙個集合,然後按規律將兩個集合進行合併。假如已知有n個人和m對好友關係 存於陣列r 如果兩個人是直接的或間接的好友關係 好友的好友的好友.則認為他們屬於同一好友圈,請求出這n個人中有幾個好友圈。例如 n 5,m 3,r 表示有5個人,1和2是...