1. 程式人生 > >常用排序演算法(三)歸併排序、堆排序、基數排序

常用排序演算法(三)歸併排序、堆排序、基數排序

歸併排序

1. 基本思想:
歸併排序是建立在歸併操作上的一種有效的排序演算法。該演算法是採用分治法的一個非常典型的應用。
首先考慮下如何將2個有序數列合併。這個非常簡單,只要從比較2個數列的第一個數,誰小就先取誰,取了後就在對應數列中刪除這個數。然後再進行比較,如果有數列為空,那直接將另一個數列的資料依次取出即可。

//將有序陣列a[]和b[]合併到c[]中
void MemeryArray(int a[], int n, int b[], int m, int c[])
{
 int i, j, k;

 i = j = k = 0;
 while (i < n && j < m)
 {
     if (a[i] < b[j])
         c[k++] = a[i++];
     else
         c[k++] = b[j++]; 
 }

 while (i < n)
     c[k++] = a[i++];

 while (j < m)
     c[k++] = b[j++];
}

解決了上面的合併有序數列問題,再來看歸併排序,其的基本思路就是將陣列分成2組A,B,如果這2組組內的資料都是有序的,那麼就可以很方便的將這2組資料進行排序。如何讓這2組組內資料有序了?
可以將A,B組各自再分成2組。依次類推,當分出來的小組只有1個數據時,可以認為這個小組組內已經達到了有序,然後再合併相鄰的2個小組就可以了。這樣通過先遞迴的分解數列再合併數列就完成了歸併排序。

2. 過程:

5550

歸併排序

3. 平均時間複雜度:O(NlogN)
歸併排序的效率是比較高的,設數列長為N,將數列分開成小數列一共要logN步,每步都是一個合併有序數列的過程,時間複雜度可以記為O(N),故一共為O(N*logN)。

4. 程式碼實現:

public static void merge_sort(int a[],int first,int last,int temp[]){

  if(first < last){
      int middle = (first + last)/2;
      merge_sort(a,first,middle,temp);//左半部分排好序
      merge_sort(a,middle+1,last,temp);//右半部分排好序
      mergeArray(a,first,middle,last,temp); //合併左右部分
  }
}
//合併 :將兩個序列a[first-middle],a[middle+1-end]合併
public static void mergeArray(int a[],int first,int middle,int end,int temp[]){     
  int i = first;
  int m = middle;
  int j = middle+1;
  int n = end;
  int k = 0; 
  while(i<=m && j<=n){
      if(a[i] <= a[j]){
          temp[k] = a[i];
          k++;
          i++;
      }else{
          temp[k] = a[j];
          k++;
          j++;
      }
  }     
  while(i<=m){
      temp[k] = a[i];
      k++;
      i++;
  }     
  while(j<=n){
      temp[k] = a[j];
      k++;
      j++; 
  }

  for(int ii=0;ii<k;ii++){
      a[first + ii] = temp[ii];
  }
}

堆排序

  1. 基本思想:

    6660

    • 圖示: (88,85,83,73,72,60,57,48,42,6)

      7770

      Heap Sort

  2. 平均時間複雜度:O(NlogN)
    由於每次重新恢復堆的時間複雜度為O(logN),共N - 1次重新恢復堆操作,再加上前面建立堆時N / 2次向下調整,每次調整時間複雜度也為O(logN)。二次操作時間相加還是O(N * logN)。

  3. java程式碼實現

//構建最小堆
public static void MakeMinHeap(int a[], int n){
 for(int i=(n-1)/2 ; i>=0 ; i--){
     MinHeapFixdown(a,i,n);
 }
}
//從i節點開始調整,n為節點總數 從0開始計算 i節點的子節點為 2*i+1, 2*i+2  
public static void MinHeapFixdown(int a[],int i,int n){

   int j = 2*i+1; //子節點
   int temp = 0;

   while(j<n){
       //在左右子節點中尋找最小的
       if(j+1<n && a[j+1]<a[j]){   
           j++;
       }

       if(a[i] <= a[j])
           break;

       //較大節點下移
       temp = a[i];
       a[i] = a[j];
       a[j] = temp;

       i = j;
       j = 2*i+1;
   }
}

public static void MinHeap_Sort(int a[],int n){
  int temp = 0;
  MakeMinHeap(a,n);

  for(int i=n-1;i>0;i--){
      temp = a[0];
      a[0] = a[i];
      a[i] = temp; 
      MinHeapFixdown(a,0,i);
  }     
}

基數排序

BinSort

  1. 基本思想:
    BinSort想法非常簡單,首先建立陣列A[MaxValue];然後將每個數放到相應的位置上(例如17放在下標17的陣列位置);最後遍歷陣列,即為排序後的結果。

  2. 圖示:

    BinSort

    • 問題: 當序列中存在較大值時,BinSort 的排序方法會浪費大量的空間開銷。

    RadixSort

    1. 基本思想: 基數排序是在BinSort的基礎上,通過基數的限制來減少空間的開銷。
    2. 過程:

      10101001

      過程1

      9990

      過程2

    (1)首先確定基數為10,陣列的長度也就是10.每個數34都會在這10個數中尋找自己的位置。
    (2)不同於BinSort會直接將數34放在陣列的下標34處,基數排序是將34分開為3和4,第一輪排序根據最末位放在陣列的下標4處,第二輪排序根據倒數第二位放在陣列的下標3處,然後遍歷陣列即可。

  3. java程式碼實現:

public static void RadixSort(int A[],int temp[],int n,int k,int r,int cnt[]){

   //A:原陣列
   //temp:臨時陣列
   //n:序列的數字個數
   //k:最大的位數2
   //r:基數10
   //cnt:儲存bin[i]的個數

   for(int i=0 , rtok=1; i<k ; i++ ,rtok = rtok*r){

       //初始化
       for(int j=0;j<r;j++){
           cnt[j] = 0;
       }
       //計算每個箱子的數字個數
       for(int j=0;j<n;j++){
           cnt[(A[j]/rtok)%r]++;
       }
       //cnt[j]的個數修改為前j個箱子一共有幾個數字
       for(int j=1;j<r;j++){
           cnt[j] = cnt[j-1] + cnt[j];
       }
       for(int j = n-1;j>=0;j--){      //重點理解
           cnt[(A[j]/rtok)%r]--;
           temp[cnt[(A[j]/rtok)%r]] = A[j];
       }
       for(int j=0;j<n;j++){
           A[j] = temp[j];
       }
   }
}