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

更新、更全的《數據結構與算法》的更新網站,更有python、go、人工智能教學等着你:http://www.javashuo.com/article/p-zfinzipt-hh.htmljava

1、集合的簡化表示

在上一節 集合及運算中,咱們對集合使用二叉樹表示,以下圖所示:python

爲了使用二叉樹,咱們在上一節中使用如下代碼,構造二叉樹:算法

/* 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;
}

2、題意理解

根據輸入樣例,以此來判斷計算機之間有多少個組成,以下圖所示框架

上圖動態變化以下圖所示:測試

下圖爲五臺計算機之間造成全鏈接狀態,所以當作一個總體:優化

3、程序框架搭建

/* 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);
}

4、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卻運行超時了,爲了解決上述問題,咱們可使用下面將要講到了按秩歸併的思想修改代碼。

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; 
  }
}

6、路徑壓縮

對於上述代碼超時的問題,咱們也可使用路徑壓縮的方法優化代,即壓縮給定元素到集合根元素路徑中的全部元素,詳細狀況以下圖所示:

上圖代碼可表示爲:

/* 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,我是懶得研究下圖所示了。

相關文章
相關標籤/搜索