排序七 歸並排序


要點

歸並排序是建立在歸並操作上的一種有效的排序算法,該算法是采用分治法(Divide and Conquer的一個非常典型的應用。

將已有序的子序列合並,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合並成一個有序表,稱為二路歸並

 

歸並排序的基本思想

將待排序序列R[0...n-1]看成是n個長度為1的有序序列,將相鄰的有序表成對歸並,得到n/2個長度為2的有序表;將這些有序序列再次歸並,得到n/4個長度為4的有序序列;如此反復進行下去,最后得到一個長度為n的有序序列。

綜上可知:

歸並排序其實要做兩件事:

1)“分解”——將序列每次折半划分

2)“合並”——將划分后的序列段兩兩合並后排序

 

我們先來考慮第二步,如何合並

在每次合並過程中,都是對兩個有序的序列段進行合並,然后排序。

這兩個有序序列段分別為 R[low, mid] R[mid+1, high]

先將他們合並到一個局部的暫存數組R2中,帶合並完成后再將R2復制回R中。

為了方便描述,我們稱 R[low, mid] 第一段,R[mid+1, high] 為第二段。

每次從兩個段中取出一個記錄進行關鍵字的比較,將較小者放入R2中。最后將各段中余下的部分直接復制到R2中。

經過這樣的過程,R2已經是一個有序的序列,再將其復制回R中,一次合並排序就完成了。

核心代碼

public  void Merge( int[] array,  int low,  int mid,  int high) {
     int i = low;  //  i是第一段序列的下標
     int j = mid + 1;  //  j是第二段序列的下標
     int k = 0;  //  k是臨時存放合並序列的下標
     int[] array2 =  new  int[high - low + 1];  //  array2是臨時合並序列

    
//  掃描第一段和第二段序列,直到有一個掃描結束
     while (i <= mid && j <= high) {
         //  判斷第一段和第二段取出的數哪個更小,將其存入合並序列,並繼續向下掃描
         if (array[i] <= array[j]) {
            array2[k] = array[i];
            i++;
            k++;
        }  else {
            array2[k] = array[j];
            j++;
            k++;
        }
    }

     //  若第一段序列還沒掃描完,將其全部復制到合並序列
     while (i <= mid) {
        array2[k] = array[i];
        i++;
        k++;
    }

     //  若第二段序列還沒掃描完,將其全部復制到合並序列
     while (j <= high) {
        array2[k] = array[j];
        j++;
        k++;
    }

     //  將合並序列復制到原始序列中
     for (k = 0, i = low; i <= high; i++, k++) {
        array[i] = array2[k];
    }
}

掌握了合並的方法,接下來,讓我們來了解  如何分解

在某趟歸並中,設各子表的長度為gap,則歸並前R[0...n-1]中共有n/gap個有序的子表:R[0...gap-1], R[gap...2*gap-1], ... , R[(n/gap)*gap ... n-1]

調用Merge將相鄰的子表歸並時,必須對表的特殊情況進行特殊處理。

若子表個數為奇數,則最后一個子表無須和其他子表歸並(即本趟處理輪空):若子表個數為偶數,則要注意到最后一對子表中后一個子表區間的上限為n-1 

核心代碼

public  void MergePass( int[] array,  int gap,  int length) {
     int i = 0;

     //  歸並gap長度的兩個相鄰子表
     for (i = 0; i + 2 * gap - 1 < length; i = i + 2 * gap) {
        Merge(array, i, i + gap - 1, i + 2 * gap - 1);
    }

     //  余下兩個子表,后者長度小於gap
     if (i + gap - 1 < length) {
        Merge(array, i, i + gap - 1, length - 1);
    }
}

public  int[] sort( int[] list) {
     for ( int gap = 1; gap < list.length; gap = 2 * gap) {
        MergePass(list, gap, list.length);
        System.out.print("gap = " + gap + ":\t");
         this.printAll(list);
    }
     return list;
}


算法分析

歸並排序算法的性能

排序類別

排序方法

時間復雜度

空間復雜度

穩定性

復雜性

平均情況

最壞情況

最好情況

歸並排序

歸並排序

O(nlog2n)

O(nlog2n)

O(nlog2n)

O(n)

穩定

較復雜

 

時間復雜度

歸並排序的形式就是一棵二叉樹,它需要遍歷的次數就是二叉樹的深度,而根據完全二叉樹的可以得出它的時間復雜度是O(n*log2n)

 

空間復雜度

由前面的算法說明可知,算法處理過程中,需要一個大小為n的臨時存儲空間用以保存合並序列。

 

算法穩定性

在歸並排序中,相等的元素的順序不會改變,所以它是穩定的算法。

 

歸並排序和堆排序、快速排序的比較

若從空間復雜度來考慮:首選堆排序,其次是快速排序,最后是歸並排序。

若從穩定性來考慮,應選取歸並排序,因為堆排序和快速排序都是不穩定的。

若從平均情況下的排序速度考慮,應該選擇快速排序。 


完整參考代碼

Java版本

 1  package notes.javase.algorithm.sort;
 2 
 3  public  class MergeSort {
 4      public  void Merge( int[] array,  int low,  int mid,  int high) {
 5          int i = low;  //  i是第一段序列的下標
 6          int j = mid + 1;  //  j是第二段序列的下標
 7          int k = 0;  //  k是臨時存放合並序列的下標
 8          int[] array2 =  new  int[high - low + 1];  //  array2是臨時合並序列
 9 
10           //  掃描第一段和第二段序列,直到有一個掃描結束
11          while (i <= mid && j <= high) {
12              //  判斷第一段和第二段取出的數哪個更小,將其存入合並序列,並繼續向下掃描
13              if (array[i] <= array[j]) {
14                 array2[k] = array[i];
15                 i++;
16                 k++;
17             }  else {
18                 array2[k] = array[j];
19                 j++;
20                 k++;
21             }
22         }
23 
24          //  若第一段序列還沒掃描完,將其全部復制到合並序列
25          while (i <= mid) {
26             array2[k] = array[i];
27             i++;
28             k++;
29         }
30 
31          //  若第二段序列還沒掃描完,將其全部復制到合並序列
32          while (j <= high) {
33             array2[k] = array[j];
34             j++;
35             k++;
36         }
37 
38          //  將合並序列復制到原始序列中
39          for (k = 0, i = low; i <= high; i++, k++) {
40             array[i] = array2[k];
41         }
42     }
43 
44      public  void MergePass( int[] array,  int gap,  int length) {
45          int i = 0;
46 
47          //  歸並gap長度的兩個相鄰子表
48          for (i = 0; i + 2 * gap - 1 < length; i = i + 2 * gap) {
49             Merge(array, i, i + gap - 1, i + 2 * gap - 1);
50         }
51 
52          //  余下兩個子表,后者長度小於gap
53          if (i + gap - 1 < length) {
54             Merge(array, i, i + gap - 1, length - 1);
55         }
56     }
57 
58      public  int[] sort( int[] list) {
59          for ( int gap = 1; gap < list.length; gap = 2 * gap) {
60             MergePass(list, gap, list.length);
61             System.out.print("gap = " + gap + ":\t");
62              this.printAll(list);
63         }
64          return list;
65     }
66 
67      //  打印完整序列
68      public  void printAll( int[] list) {
69          for ( int value : list) {
70             System.out.print(value + "\t");
71         }
72         System.out.println();
73     }
74 
75      public  static  void main(String[] args) {
76          int[] array = {
77                 9, 1, 5, 3, 4, 2, 6, 8, 7
78         };
79 
80         MergeSort merge =  new MergeSort();
81         System.out.print("排序前:\t\t");
82         merge.printAll(array);
83         merge.sort(array);
84         System.out.print("排序后:\t\t");
85         merge.printAll(array);
86     }
87 }
View Code


運行結果
 

排序前:     9   1   5   3   4   2   6   8   7  
gap = 1:   1   9   3   5   2   4   6   8   7  
gap = 2:   1   3   5   9   2   4   6   8   7  
gap = 4:   1   2   3   4   5   6   8   9   7  
gap = 8:   1   2   3   4   5   6   7   8   9  
排序后:     1   2   3   4   5   6   7   8   9  


參考資料

《數據結構習題與解析》(B級第3版) 


相關閱讀

歡迎閱讀 程序員的內功——算法 系列 

示例源碼:https://github.com/dunwu/algorithm-notes


免責聲明!

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



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