資料結構之並查集
阿新 • • 發佈:2019-02-17
1.概述
英文:DisjointSet or(Union-find set),即“不相交集合”將編號分別為1…N的N個物件劃分為不相交集合,在每個集合中,選擇其中某個元素代表所在集合。
常見兩種操作:
(1)合並兩個集合 (2)查找某元素屬於哪個集合 所以也成稱為並查集。。2.導引問題
Problem Description 某省調查城鎮交通狀況,得到現有城鎮道路統計表,表中列出了每條道路直接連通的城鎮。省政府“暢通工程”的目標是使全省任何兩個城鎮間都可以實現交通(但不一定有直接的道路相連,只要互相間接通過道路可達即可)。問最少還需要建設多少條道路? Input 測試輸入包含若干測試用例。每個測試用例的第1行給出兩個正整數,分別是城鎮數目N ( < 1000 )和道路數目M;隨後的M行對應M條道路,每行給出一對正整數,分別是該條道路直接連通的兩個城鎮的編號。為簡單起見,城鎮從1到N編號。注意:兩個城市之間可以有多條道路相通,也就是說
3 3
1 2
1 2
2 1
這種輸入也是合法的
當N為0時,輸入結束,該用例不被處理。 Output 對每個測試用例,在1行裡輸出最少還需要建設的道路數目。
如果遇到這題,我們該如何去做呢?
3.並查集引入
初始化操作:
我們需要一個 void makeset (int n)n表示初始化的範圍
void makeset(int n)
{
for(int i = 0; i <= n; i++)
father[i] = i;
}
查詢操作:
假如我們現在有要在這個集合中尋找一個元素x所處的集合(該元素的符節點),那麼我們只需要一個 int findset(int a)函式
findset返回的值為該元素根節點的下標//遞迴寫法 int findset(int a){ if(a==father[a]) return a; return findset(father[a]); } //迭代寫法 int findset(int x){ int p = x; while(p!=father[p]) p = father[p]; return p; }
合併操作:
假如 x 和 y開始是屬於不同集合的元素,現在要把它們併到一起,那麼需要一個 void unionset (int x , int y)函式
void unionset(int x, int y)
{
int a = findset(x);
int b = findset(y);
if(a != b) father[a] = b;
}
有了以上關於並查集的基礎,相信都可以寫HDU1232這道題了。。
#include <iostream> #define SIZE 10005 using namespace std; int father[SIZE]; void makeset(int n) { for(int i = 0; i <= n; i++) father[i] = i; } int findset(int a){ if(a == father[a]) return a; return findset(father[a]); } void unionset(int x, int y) { int a = findset(x); int b = findset(y); if(a != b) father[a] = b; } int main() { int n, m; int x, y; int ans = 0; while(cin>>n>>m) { makeset(n); for(int i = 1; i <= m; i++) { cin>>x>>y; unionset(x,y); } for(int i = 1; i <= n; i++) { if(father[i] == i) ans++; } ans--; cout<<ans<<endl; ans = 0; } }
4.並查集的優化
Find_Set(x)時 路徑壓縮
尋找祖先時我們一般採用遞迴查詢,但是當元素很多亦或是整棵樹變為一條鏈時,每次Find_Set(x)都是O(n)的複雜度,有沒有辦法減小這個複雜度呢?
答案是肯定的,這就是路徑壓縮,即當我們經過"遞推"找到祖先節點後,"回溯"的時候順便將它的子孫節點都直接指向祖先,這樣以後再次Find_Set(x)時複雜度就變成O(1)了,如下圖所示;可見,路徑壓縮方便了以後的查詢。
Union(x,y)時 按秩合併
即合併的時候將元素少的集合合併到元素多的集合中,這樣合併之後樹的高度會相對較小。
int findset (int a)
//遞迴寫法
int findset(int a){
if(a==father[a])
return a;
else {
int temp = father[a];
father[a] = findset(father[a]);
}
return father[a]; //father[a] 已經是根節點,直接返回就行了
}
//非遞迴的方式進行路徑壓縮,更加直觀一些
int findset(int x){
int p = x,temp;
while(p!=father[p]) p = father[p];
//fatherpath comfatherression
while(x != p){
temp = father[x];
father[x] = p;
x = temp;
}
return p;
}
5.最終模板
void makeset(int n){
int i;
for(i=1;i<=n;i++){
father[i] = i;
rank[i] = 1;
}
}
int findset(int a){
if(a==father[a])
return a;
else {
int temp = father[a];
father[a] = findset(father[a]);
rank[a] = (rank[temp]+rank[a]+1)%2 ;
//必須有,更新路徑壓縮之後a與根結點之間的關係; father改變,rank就必須要跟著改變
}
return father[a];
}
void unionset(int a,int b){
int fa,fb;
fa = findset(a);
fb = findset(b);
if(fa!=fb){
father[fa] = fb;
rank[fa] = (rank[a]+rank[b])%2 ; //fa結點以下的結點的rank不需要改
}
}
6相關題目練習
待更新
參考目錄:
http://www.cnblogs.com/cherish_yimi/archive/2009/10/11/1580839.html