小白專場: File Transfer--集合的簡化表示,按秩歸併與路徑壓縮
阿新 • • 發佈:2018-11-22
集合的簡化表示
原始的集合表示:
typedef struct {
ElementType Data;
int Parent;
} SetType;
int Find(SetType S[], Elemtype X)
{ //在陣列S中查詢值為X的元素所屬的集合
//MaxSize是全域性變數,為陣列S的最大長度
int i;
for (i = 0; i < MaxSize && S[i].Data != X; i++);
if (i >= MaxSize) return -1; //未找到X,返回-1
for (; S[i].Parent >= 0; i = S[i].Parent);
return i;//找到X所屬集合,返回樹根結點在陣列S中的下標
}
集合的簡化表示:
任何有限集合的(N個)元素都可以被一一對映為整數0 ~ N–1
集合中元素在樹結構中的排列沒有順序,所以可以用下標代替代替該元素,每一個下標對應一個元素,形成一種一一對應的關係
typedef int ElementType; /*預設元素可以用非負整數表示*/
typedef int SetName; /*預設用根結點的下標作為集合名稱*/
typedef ElementType SetType[MaxSize];
SetName Find(SetType S, ElementType X)
{ /* 預設集合元素全部初始化為-1 */
for (; S[X] >= 0; X = S[X]);
return X;
}
void Union(SetType S, SetName Root1, SetName Root2)
{ /* 這裡預設Root1和Root2是不同集合的根結點*/
S[Root2] = Root1;
}
題目:
第一行,表示集合中有幾個元素
C 3 2:檢查3和2是否屬於同一集合
I 3 2:將3和2所在集合合併
S:程式結束,如果只存在一個集合,輸出:The network is connected.如果有n個集合,輸出:There are %d components.
程式框架搭建
int main()
{
初始化集合;
do {
讀入一條指令;
處理指令;
} while (沒結束);
return 0;
}
int main()
{
SetType S;
int n;
char in;
cin>>n;
Initialization(S, n);//每一個元素都是一個集合,全部初始化為-1
do {
cin>>in;
switch (in) {
case 'I': Input_connection(S); break;
case 'C': Check_connection(S); break;
case 'S': Check_network(S, n); break;
}
} while (in != 'S');
return 0;
}
//將兩個元素所在集合合併
void Input_connection(SetType S)
{
ElementType u, v;
SetName Root1, Root2;
cin>>u>>v;
Root1 = Find(S, u - 1);
Root2 = Find(S, v - 1);
if (Root1 != Root2)
Union(S, Root1, Root2);
}
//檢查兩個元素是否屬於一個集合
void Check_connection(SetType S)
{
ElementType u, v;
SetName Root1, Root2;
cin>>u>>v;
Root1 = Find(S, u - 1);
Root2 = Find(S, v - 1);
if (Root1 == Root2)
printf("yes\n");
else printf("no\n");
}
//檢視最後一共有幾個集合
void Check_network(SetType S, int n)
{
int i, counter = 0;
for (i = 0; i<n; i++)
if (S[i] < 0) counter++;
if (counter == 1)
printf("The network is connected.\n");
else
printf("There are %d components.\n", counter);
}
按秩歸併
為什麼要按秩歸併
舉例說明:一直將元素1所在集合合併到其他集合,整個樹的結構就會失衡,樹高度較大,Find()時間複雜度為O(n),對n個結點操作,複雜度為O(n2)
解決方法1:把較矮的樹合併到較高的樹上去
將樹的高度存放在根節的元素中
S[Root]= - 樹高
S[Root]初始化時,仍然初始化為-1
if (S[Root2] < S[Root1]) //S[Root2]高度大,集合1合併到集合2
S[Root1] = Root2;
else
{
if (S[Root1] == S[Root2]) S[Root1]--;//集合高度相等,合併後高度增加
S[Root2] = Root1;
}
最壞情況下,每次合併高度都相等,樹高 = O(log N)
解決方法2:比規模,把小樹貼到大樹上
S[Root]= - 元素個數;
void Union(SetType S, SetName Root1, SetName Root2)
{
if (S[Root2]<S[Root1]) //S[Root1]所在樹規模較小
{
S[Root2] += S[Root1];//樹的規模變為二者元素個數之和
S[Root1] = Root2;
}
else
{
S[Root1] += S[Root2];
S[Root2] = Root1;
}
}
兩種方法統稱“按秩歸併”
路徑壓縮
SetName Find(SetType S, ElementType X)
{
if (S[X] < 0) //找到集合的根
return X;
else
return S[X] = Find(S, S[X]);
}
這段程式碼幹了三件事
- 先找到根;
- 把根變成X 的父結點;
- 再返回根。
路徑壓縮第一次執行的時間比較長,但是如果頻繁使用查詢命令,第一次將路徑壓縮,大大減小樹的高度,後續查詢速度將大大增加