小白專場-FileTransfer-c語言實現


更新、更全的《數據結構與算法》的更新網站,更有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]);
}

總之上述代碼干了這三件事:

  1. 先找到根;
  2. 把根變成X的父結點;
  3. 再返回根

因此,路徑壓縮第一次執行的時間比較長,但是如果頻繁使用查找命令,第一次將路徑壓縮,大大減小樹的高度,后續查找速度將大大增加

6.1 路徑壓縮時間復雜度計算

由於pta並沒有嚴格控制時間限制,使用java這種語言,不使用路徑壓縮,問題不大,我寫這個也只是為了回顧算法,來放松放松,不是為了折騰自己,因此。

給你一個眼神自己體會,給你一個網址親自體會https://www.icourse163.org/learn/ZJU-93001?tid=1206471203#/learn/content?type=detail&id=1211167097&sm=1,我是懶得研究下圖所示了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM