1. 程式人生 > >資料結構:歸併排序(非遞迴)

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

前言

歸併排序,是建立在歸併操作上的一種有效的排序演算法,效率為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陣列後端。

剩下的應該不難理解了吧。