1. 程式人生 > >C++並查集

C++並查集

並查集,大概意思上就是說個體a和個體b是否為同一個資料集中,如果不是,那麼就要想辦法將這兩個集合合併起來,這樣總的集合數目就由2變成1。在C++中,並查集的基本問題就是求一個圖的連通子圖數問題,具體可參考九度online Problem 1012 暢通工程問題。
該問題的主要意思是,在一個地圖上有若干個城鎮,每個城鎮可以看作是一個點,如果城鎮之間可以直接或者間接的連通(例如a和b相連,b和c相連,那麼a和c也是相連),這些城鎮就可以看作是一個集合,如果不能連通,就屬於不同的集合。問題是要求出在地圖上還需要修建幾條路,能夠將所有的城鎮都連通起來,而不需要要求具體寫出到底要在哪兩個城鎮之間修路。
在一開始看到這個問題的時候,我的第一反應就是可以用迪傑斯特拉演算法來求。將一個城鎮設為起點v,如果v到其他城鎮有距離,則不需要修路,如果沒有,則需要修路,即修路個數+1。後來我又發現,這個不行,因為迪傑斯特拉演算法只是求單源點到其它點的最短路徑,例如,起點v和點w想通,k和j想通,但無論是v還是w都和k或者j都不相通,那麼按照迪傑斯特拉演算法,需要修兩條路,其實只需要一條就行了。所以,該演算法不行。
後來看了別人的評論,才發現是要求連通子圖數的問題。而求解這類問題,有個最簡單方便的演算法,就是並查集,在網上詳細瞭解之後,發現這個演算法確實十分的高效簡潔,特將個人的一點想法分享給大家。
int find(int x)
{
int r = x;
while (pre[r] != r)
{
r = pre[r];
}
int i = x, j;
while (i != r)
{
j = pre[i];
pre[i] = r;
i = j;
}
return r;
}


其中,陣列Pre用來儲存每個節點的父節點,find函式用來查詢節點的根節點是哪個,並且,在查詢的過程中,使用了路徑壓縮的方法,就是說,加入a的父節點是b,b的父節點是c,c的父節點是d,d為根節點,那麼,a,b,c的根節點就是d。我們在find(a)的過程中,首先是找到a的根節點為d,然後進行路徑壓縮
int i = x, j;
while (i != r)
{
j = pre[i];
pre[i] = r;
i = j;
}

這段程式碼就是進行路徑壓縮的。首先,我們保留了a的節點資訊,並賦值給i,然後開始壓縮路徑,因為此時a!=d,所以進行while迴圈,首先找到a的父節點為b並賦值給j,然後讓a的父節點直接指向根節點d,這時候讓a原來的父節點b賦值給i,再次判斷此時有沒有到a原來的根節點就是d,不是,繼續進行,此時找到b的父節點為c賦值給j,接著同樣將c的父節點直接指向根節點d,以此類推。直到找到原來的根節點為止。
大概意思,用更簡單的例子講,就是,d的手下是c,c的手下是b,b的手下是a,在壓縮過程中,直接讓a b c三個手下全部成為d的一級手下,這就是路徑壓縮的原理。