並查集(UnionSet)是一種樹型的數據結構,用於處理一些不相交集合)的合並及查詢問題。常常在使用中以森林來表示。
並查集實現了將N個不同的元素分成一組不相交的集合。開始時,每個元素就是一個集合,然后按規律將兩個集合進行合並。
比如:現在有 0,1,2,3,4,5,6,7,8,9 總共10個元素。它們組成了3個集合,如圖:

開始時,將每個元素都當做一個單元素的集合,並將每個元素都初始化為-1,如圖:

根據集合按規律將兩兩集合進行合並, 6、7、8屬於0這個集合,所以把6、7、8這三個位置都置0,而0這個位置減去3;1和2這兩個集合類似,4和9都屬於1這個集合,所以將4、9這兩個位置都置為1,並將位置1的數據減2;3、5同屬於集合2,所以將位置3、5的數據置為2,將位置2的數據減去2,即:

這樣我們可以根據負數的個數清晰的看出有幾個集合,並且我們也可以清晰的看出某節點是屬於哪個集合的。有3個負數,所以有3個集合,3、5位置數據為2,所以它們屬於集合2;6、7、8位置的數據都為0,所以它們都屬於集合0;4、9屬於集合集合1.
集合的合並:
形如:

把集合1合並到集合0中,集合1有1、4、9三個元素,所以,將位置1設置為0,並將位置0的數據減去3,即:
由上圖我們可以看出4、9屬於集合1,集合1又屬於集合0;此時數組中共有2個負數,因此有兩個集合!
代碼實現:
class UnionSet
{
public:
UnionSet(size_t size)
:_size(size)
, _array(new int[size])
{
for (int i = 0; i < size; i++)
{ //將集合的每個元素都初始化為-1
_array[i] = -1;
}
}
//合並兩個集合
void Merge(int root1, int root2)
{
//找到root1所在集合的代表元素和root2所在集合的代表元素,鏈接
while (_array[root2] >= 0)
{
root2 = _array[root2];
}
while (_array[root1] >= 0)
{
root1 = _array[root1];
}
_array[root1] += _array[root2];
_array[root2] = root1;
}
//查找root對應集合的(根)代表元素
int Find(int root)
{
while (_array[root]>=0)
{
root = _array[root];
}
return root;
}
//打印
void Print()
{
for (int i = 0; i < _size; i++)
{
cout << _array[i] << " ";
}
cout << endl;
}
public:
int* _array;
size_t _size;
};

筆試題:
假如已知有n個人和m對好友關系(存於數組r),如果兩個人是直接的或間接的好友關系(好友的好友的好友....),則認為他們屬於同一好友圈,請求出這n個人中有幾個好友圈。
例如:n=5,m=3,r={{1,2},{2,3},{4,5}},表示有5個人,1和2是好友,2和3是好友,4和5是好友,則1.2.3屬於一個朋友圈,4.5屬於一個另朋友圈,結果為兩個朋友圈。
最后請分析所寫代碼的時間、空間復雜度。
這個題用利用並查集實現會比較簡易和高效!
需要建立一個函數來計算朋友圈的個數:
//計算朋友圈個數
int friends(int n, int m, int r[][2]) //n元素個數,m為每個朋友圈元素個數
{
UnionSet uf(n + 1);
//初始化朋友圈
for (int i =0 ; i <= m; i++)
{
int first = r[i][0];
int second = r[i][1];
uf.Merge(first, second);
}
uf.Print();
//計算朋友圈的個數
int count = 0;
for (int i = 1; i <= n; i++)
{
if (uf._array[i] < 0)
{
count++;
}
}
return count;
}

代碼地址: https://github.com/Lynn-zhang/Data-Structure/blob/master/UnionSet.cpp
