資料結構:歸併排序(非遞迴)

窗外蟋蟀發表於2019-01-01

前言

歸併排序,是建立在歸併操作上的一種有效的排序演算法,效率為O(nlogn)​。1945年由約翰·馮·諾伊曼首次提出。該演算法是採用分治法的一個非常典型的應用,且各層分治遞迴可以同時進行。(引自維基百科)

原理

以上視訊轉自www.youtube.com/watch?v=JSc…

由於掘金中無法載入視訊,可以直接點選YouTube(需vpn)連結,也可到我部落格的原文連結中觀看perkyrookie.github.io/PerkyRookie…

用一張圖片簡單來說就是這樣

資料結構:歸併排序(非遞迴)

示例程式碼(非遞迴)

  #include <stdio.h>
  #include <stdlib.h>
  #define MAXSIZE 100 //用於要排序陣列的最大值 typedef struct      //定義一個順序表結構 
  {
      int r[MAXSIZE+1];   //用於儲存要排序陣列,r[0]用作哨兵或者臨時變數 
      int length;         //用於儲存順序表的最大長度 
  }SqList;
  ​
  void Merge(int *S, int *T, int low, int m, int high);
  void MergePass(int *S, int *T, int s, int length);
  void MergeSort(SqList *L);
  ​
  int main()
  {
      int i;
      int array[] = {39,80,76,41,13,29,50,78,30,11,100,7,41,86};
      SqList L;
      L.length = sizeof(array)/sizeof(array[0]);  //獲取陣列長度 
   
      for(i=0;i<L.length;i++)
          L.r[i+1]=array[i];  //把陣列存入順序表結構 
  ​
      MergeSort(&L);
      
      for(i=0;i<L.length;i++)     //輸出排序後的陣列 
          printf("%d ",L.r[i+1]);
  ​
      return 0;
  }
  ​
  void MergeSort(SqList *L)
  {   
      //申請額外的空間,由於r[0]為哨兵,所以這裡長度要+1
      int* TR = (int *)malloc((L->length+1)*sizeof(int));
      int k = 1;/*k用來表示每次k個元素歸併*/
      
      while (k < L->length)
      {
          //k每次乘以2是遵循1->2->4->8->16...的二路歸併元素個數的原則
          MergePass(L->r, TR, k, L->length);/*先借助輔助空間歸併*/
          k *= 2;
          MergePass(TR, L->r, k, L->length);/*再從輔助空間將排過序的序列歸併回原陣列*/
          k *= 2;
      }
      free(TR);//釋放記憶體
      TR=NULL;
  }
  //將S中相鄰長度為s的子系列兩兩歸併到T中
  //這段程式碼稍微難理解點,文章後面會分析下
  void MergePass(int *S, int *T, int s, int length)
  {
      int i = 1;  //r[0]用作哨兵或者臨時變數
      int j;
      
      while (i <= length-2*s+1)   //length-i+1(未合併元素) >= 2s 
      {       
          Merge(S, T, i, i+s-1, i+2*s-1);
          i= i+2*s;   //自增的下一個元素的起始點
      }
      if (i< length-s+1)  //歸併最後兩個序列 
          Merge(S, T, i, i+s-1, length);
      else
          for (j = i; j <= length; j++)
              T[j] = S[j];
  }
  ​
  //歸併排序最主要實現函式
  //將有序的S[low..m]和S[m+1..high]歸併到有序的T[low..high]中
  void Merge(int *S, int *T, int low, int m, int high)
  {//j在[m+1,high]區間遞增,k用於目標T的下標遞增, l是輔助變數
      int j, k, l;
      
      for (k = low,j = m+1; low <= m && j <= high; k++)   //將S中的元素由小到大歸併到T中 
      {
          if (S[low] < S[j])
              T[k] = S[low++];    //此處先執行T[k] = S[low],然後low++;
          else
              T[k] = S[j++];      //同理
      }
      
      //for迴圈過後,可能有j>high 或者 low>m,故分情況處理
      if (low <= m)
      {
          for (l = 0; l <= m-low; l++)
              T[k+l] = S[low+l];      //將剩餘的S[low..m]複製到T中 
      }
      
      if (j <= high)
      {
          for (l = 0; l <= high-j; l++)
              T[k+l] = S[j+l];        //將剩餘的S[j..high]複製到T中
      }
  }複製程式碼

這裡對部分難以理解的程式碼做個詳細分析:

  void MergePass(int *S, int *T, int s, int length)
  {
      int i = 1;  //r[0]用作哨兵或者臨時變數
      int j;  
      while (i <= length-2*s+1)   //length-i+1(未合併元素) >= 2s 
      {       
          Merge(S, T, i, i+s-1, i+2*s-1);
          i= i+2*s;   //自增的下一個元素的起始點
      }
      if (i< length-s+1)  //歸併最後兩個序列 
          Merge(S, T, i, i+s-1, length);
      else
          for (j = i; j <= length; j++)
              T[j] = S[j];
  }複製程式碼

第5~9行:這裡實現的是子序列的兩兩歸併。

  • 判斷條件i <= length-2*s+1可以看做length-i+1 >= 2s,表示未合併的元素小於2s時跳出迴圈,即不足以構成兩個長度為s的子序列時跳出迴圈;

  • 未合併元素需要+1的原因可以用個例子解釋:即第一次進入迴圈時,應該是有length個元素未合併,而i起始大小是1,所以這裡要+1。

  • 第7行:傳入元素-1是因為i+si+2s都是下一個序列的起始點,所以是一個序列的終點就是這兩個值-1。

第10~14行:判斷條件i< length-s+1一樣可以看做length-i+1>s

  • 滿足條件:未合併的元素長度大於s,表示還能構成一個長度為s的序列和一個小於s的序列,即最後兩個序列,這時還要執行一次歸併。

  • else:未合併的元素長度小於s,則只剩一個序列,這時無法歸併,直接把元素加到T陣列後端。

剩下的應該不難理解了吧。資料結構:歸併排序(非遞迴)


轉載請宣告: 

文章作者:窗外蟋蟀 

原始連結:https://juejin.im/post/5c2b41e55188256b9e0f2f0c


相關文章