更新、更全的《數據結構與算法》的更新網站,更有python、go、人工智能教學等着你:https://www.cnblogs.com/nickchen121/p/11407287.html
一、集合的簡化表示
在上一節 集合及運算中,我們對集合使用二叉樹表示,如下圖所示:
為了使用二叉樹,我們在上一節中使用以下代碼,構造二叉樹:
/* c語言實現 */
typedef struct{
ElementType Data;
int Parent;
} SetType;
int Find(SetType S[], ElementType 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中的下標
}
使用二叉樹構造集合,Find操作在差的情況下時間復雜度可能為\(O(n^2)\)
因此對於任何有限集合的(N個)元素都可以被一一映射為整數 0~N-1。即對於集合 {2, 5, 4, 3} 和 {6, 0, 1} 我們可以使用如下圖所示的數組表示:
對於上述的數組,我們可以使用如下代碼構造:
/* c語言實現 */
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語言實現 */
int main()
{
初始化集合;
do {
讀入一條指令;
處理指令;
} while (沒結束);
return 0;
}
int main()
{
SetType S;
int n;
char in;
scanf("%d\n", &n);
Initialization(S, n);
do {
scanf("%c", &in);
switch (in) {
case 'I': Input_connection(S); break; // Union(Find)
case 'C': Check_connection(S); break; // Find
case 'S': Check_network(S, n); break; // 數集合的根,判斷計算機網絡的組成個數
}
} while (in != 'S');
return 0;
}
3.1 Input_connection
/* c語言實現 */
void Input_connection(SetType S)
{
ElementType u, v;
SetName Root1, Root2;
scanf("%d %d\n", &u, &v);
Root1 = Find(S, u-1);
Root2 = Find(S, v-1);
if (Root1 != Root2)
Union(S, Root1, Root2);
}
3.2 Check_connection
/* c語言實現 */
void Check_connection(SetType S)
{
ElementType u, v;
scnaf("%d %d\n", &u, &v);
Root1 = Find(S, u-1);
Root2 = Find(S, v-1);
if (Root1 == Root2)
printf("yes\n");
else printf("no\n");
}
3.3 Check_network
/* c語言實現 */
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);
}
四、pta測試
/* c語言實現 */
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;
}
對於上述的代碼,如果我們放入pta中測試,會發現測試點6運行超時,如下圖所示:
因此,我們會考慮是不是因為出現了某種情況,導致Root2為根結點的樹過大了,因此我們修改代碼為:
/* c語言實現 */
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;
S[Root1] = Root2;
}
發現更換代碼后,測試點5卻運行超時了,為了解決上述問題,我們可以使用下面將要講到了按秩歸並的思想修改代碼。
五、按秩歸並
為什么需要按秩歸並呢?因為我們使用pta測試程序,發現代碼總是超時,因此我們可以考慮是否出現這種情況——我們再不斷地往一顆樹上累加子樹,如下圖所示:
/* c語言實現 */
Union(Find(2), Find(1));
Union(Find(3), Find(1));
……;
Union(Find(n), Find(1));
從上圖可以看出,此過程的時間復雜度為:\(T(n) = O(n^2)\)
除了上述這種情況,會導致樹的高度越來越高,如果我們把高樹貼在矮樹上,那么樹高也會快速增長,因此我們應該考慮把矮樹貼在高數上。
對於上述問題的解決,我們給出以下兩個解決方法,這兩種方法統稱為按秩歸並。
5.1 方法一:樹高替代
為了解決上述問題,我們可以把根結點從-1替代為-樹高,代碼如下:
/* c語言實現 */
if ( Root2高度 > Root1高度 )
S[Root1] = Root2;
else {
if ( 兩者等高 ) 樹高++;
S[Root2] = Root1;
}
if ( S[Root2] < S[Root1] )
S[Root1] = Root2;
else {
if ( S[Root1]==S[Root2] ) S[Root1]--;
S[Root2] = Root1;
}
5.2 方法二:規模替代
為了解決上述問題,我們也可以把根結點從**-1替代為-元素個數(把小樹貼到大樹上),代碼如下:
/* c語言實現 */
void Union( SetType S, SetName Root1, SetName Root2 )
{
if ( S[Root2]<S[Root1] ){
S[Root2] += S[Root1];
S[Root1] = Root2;
} else {
S[Root1] += S[Root2];
S[Root2] = Root1;
}
}
六、路徑壓縮
對於上述代碼超時的問題,我們也可以使用路徑壓縮的方法優化代,即壓縮給定元素到集合根元素路徑中的所有元素,詳細情況如下圖所示:
上圖代碼可表示為:
/* c語言實現 */
SetName Find(SetType S, ElementType X)
{
// 找到集合的根
if (S[X] < 0)
return X;
else
return S[X] = Find(S, S[X]);
}
總之上述代碼干了這三件事:
- 先找到根;
- 把根變成X的父結點;
- 再返回根
因此,路徑壓縮第一次執行的時間比較長,但是如果頻繁使用查找命令,第一次將路徑壓縮,大大減小樹的高度,后續查找速度將大大增加
6.1 路徑壓縮時間復雜度計算
由於pta並沒有嚴格控制時間限制,使用java這種語言,不使用路徑壓縮,問題不大,我寫這個也只是為了回顧算法,來放松放松,不是為了折騰自己,因此。
給你一個眼神自己體會,給你一個網址親自體會https://www.icourse163.org/learn/ZJU-93001?tid=1206471203#/learn/content?type=detail&id=1211167097&sm=1,我是懶得研究下圖所示了。