並查集的實現與優化
班上有 n 名學生。其中有些人是朋友,有些則不是。他們的友誼具有是傳遞性。如果已知 a 是 b 的朋友,b 是 c 的朋友,那麼我們可以認為 a 也是 c 的朋友。所謂的朋友圈,是指所有朋友的集合。讀完題目要求,暫時能想到的是,如果要解決該問題,首先要判斷出在這給定乙個 n * n 的矩陣 m,表示班級中學生之間的朋友關係。如果m[i][j] = 1,表示已知第 i 個和 j 個學生互為朋友關係,否則為不知道。你必須輸出所有學生中的已知的朋友圈總數。
n
名學生中誰跟誰是朋友關係,其次是,對於關係的劃分,有哪些學生屬於這一組,而哪些學生屬於另外一組。那基本的資料組織和資料操作該如何實現呢?
並查集,作為一種用於解決不相交集的合併及查詢問題的樹型資料結構,非常適合用來處理類似於上述例子的問題。顧名思義,並查
代表著兩種不同的操作,並
用來表示合併兩個不同集合的操作,查
用來表示檢索兩個不同元素是否屬於同乙個集合。
如果按照並查集的定義進行資料結構和資料操作的宣告,那就有了以下**實現
typedef
struct unionset unionset;
unionset *
initunionset
(int n)
return u;
}int
find
(unionset *u,
int x)
void
merge
(unionset *u,
int a,
int b)
}return;}
void
clear
(unionset *u)
以連續的儲存單元來儲存所有的個體元素,對應下標位置空間中的資料表示該個體元素所屬的組號。上述**稱為quick_find
演算法,可以看出,對於find
操作,只需要o(1)
的時間複雜度,而merge
操作,因為需要從所有個體中找到滿足要求的個體,所以需要遍歷整個開闢的記憶體空間,其搜尋一次的時間複雜度為o(n)
,但如果問題規模很大,搜尋m
次的時間複雜度將會變為o(mn)
,這對於較多資料的處理是很不友好的,因此需要對merge
操作進行優化。
在quick_find
演算法實現中,處理合併屬於同一組的元素時,是將所有的屬於同一組的個體對應位置的資料修改為同乙個標記,這使得每個個體之間沒有任何的聯絡特徵,只能通過所持有的數字是否相同來判斷是否屬於同一集合。如果能通過乙個元素找到同組的另外乙個元素,這樣就可以省去遍歷所有元素的時間。
按照這樣的想法,就可以得到下述稱為quick_union
的演算法
typedef
struct unionset unionset;
unionset *
initunionset
(int n)
return u;
}int
find
(unionset *u,
int x)
void
merge
(unionset *u,
int a,
int b)
void
clear
(unionset *u)
quick_union
演算法的核心思想為,判斷兩個不同元素所屬組的代表元素是否相同,所謂的代表元素,可以簡單的理解為乙個樹的根結點,若兩個元素所屬組的根節點相同,則可以認為這兩個元素是屬於同乙個組的,若不相同,只需要將其中一顆樹的根結點作為另一顆樹的葉子節點即可。
大家仔細想一想,樹這種資料結構,很容易因插入新節點的順序而產生樹的不平衡問題,當插入節點順序滿足一定要求時,甚至整棵樹會退化至乙個鍊錶,這時搜尋樹中資料的時間複雜度就會退化為o(n)
。在quick_union
演算法的merge
操作中,是可能存在樹退化為鍊錶的情況的,例如將一棵高度為n
的樹作為高度為1
的樹的子樹,如此反覆,最後將會產生一棵實際退化為鍊錶的不平衡樹。
要想解決樹的不平衡問題,在進行merge
操作時,就可以事先判斷兩個樹的節點數量,始終將節點數較少的一方作為另一方的子樹,這樣就可以在一定程度上解決樹的不平衡問題,從而提高find
和merge
的效率。這樣的處理形成的演算法稱為weighted quick union
演算法。**如下
typedef
struct unionset unionset;
unionset *
initunionset
(int n)
return u;
}int
find
(unionset *u,
int x)
void
merge
(unionset *u,
int a,
int b)
void
clear
(unionset *u)
其中新新增的size
陣列用來儲存根節點所屬組中節點的數量。
在完成weighted quick union
演算法對不平衡樹問題的優化後,是不是可以再進一步的優化呢,如果乙個新插入的葉子節點到所屬組的根結點之間的距離能夠進一步縮短的話,這樣就既可以降低merge
的時間複雜度,又可以提高find
的效率。這樣的優化在並查集中稱為路徑的壓縮,即盡可能的使樹的高度扁平化,讓葉子節點直接掛到根結點上,使合併和查詢的時間複雜度都接近於o(1)
。此時優化後的演算法稱為weighted quick union with path compress
,其實只需要在weighted quick union
演算法的基礎上修改find
操作,就可以大大的提高演算法的執行效率。
int
find
(unionset *u,
int x)
至此,並查集的實現與優化就完成了,回顧所有的優化思路,可以總結為:quick_find
->quick_union
->weighted quick union
->weighted quick union with path compress
,對於並查集可以用來處理哪一類的問題、並查集是怎麼一步步進行優化的,還要不斷的刷題進一步體會其中的精髓。 並查集的實現及優化
並查集是一種用於在森林中判斷子圖數量及點的歸屬的資料結構,由於其特殊的路徑壓縮操作,使得這一過程可以異常地快。並查集主要由乙個pre陣列以及兩個函式組成 find函式和join函式。pre陣列表示每一節點的前驅,最終已完成的並查集,每乙個子圖的所有點只有乙個前驅 這也是其高效的原因 而初始化的並查集...
kruskal 並查集優化
這兩天搞dp搞的快暴了,想學學網路流。拿過算導來一看,最短路還沒整完呢。寫了乙個用並查集優化的kruskal演算法,並查集是用非遞迴的狀態壓縮實現的。詳見 kruskal沒有用堆優化,不是我不想,而是實在不會。所以直接用sort按權值排了下序,時間複雜度o n n logn my code incl...
並查集的優化措施
include include include include define maxn 10000 int a maxn int find int x void union int x,int y int main 這是基礎的並查集,效率略低,如果樹的高度過高就會很慢 include include...