1. 程式人生 > >八大排序演算法詳解——歸併排序

八大排序演算法詳解——歸併排序

基本思想

n個記錄的檔案的直接選擇排序可經過n-1趟直接選擇排序得到有序結果:

  1. 初始狀態:無序區為R[1..n],有序區為空。
  2. 第1趟排序: 在無序區R[1..n]中選出關鍵字最小的記錄R[k],將它與無序區的第1個記錄R[1] 交換,使R[1..1]和R[2..n]分別變為記錄個數增加1個的新有序區和記錄個數減少1個的新無序區。
  3. ……
  4. 第i趟排序: 第i趟排序開始時,當前有序區和無序區分別為R[1..i-1]和R[i..n](1≤i≤n-1)。 該趟排序從當前無序區中選出關鍵字最小的記錄R[k],將它與無序區的第1個記錄R[i]交換,使R[1..i] 和R[i+1..n]分別變為記錄個數增加1個的新有序區和記錄個數減少1個的新無序區。

這樣,n個記錄的檔案的直接選擇排序可經過n-1趟直接選擇排序得到有序結果。

演算法實現

歸併排序演算法,Java實現,程式碼如下所示:

public abstract class Sorter {
             public abstract void sort(int[] array);
        }

        public class MergeSorter extends Sorter {

             @Override
             public void sort(int[] array) {
                  int
[] auxArray = new int[array.length]; mergeSort(array, auxArray, 0, array.length - 1); } /** * 基於分治思想,執行歸併排序 * @param low 待排序陣列下標下界 * @param high 待排序陣列下標上界 */ private void mergeSort(int[] array, int
[] auxArray, int low, int high) { int dividedIndex = 0; // 分治位置索引變數 if (low < high) { dividedIndex = (low + high) / 2; // 計算分治位置(採用簡單的二分思想) mergeSort(array, auxArray, low, dividedIndex); // 左側遞迴歸併排序 mergeSort(array, auxArray, dividedIndex + 1, high); // 右側遞迴歸併排序 merge(array, auxArray, low, dividedIndex, high); // 合併分治結果 } } private void merge(int[] array, int[] auxArray, int low, int dividedIndex, int high) { int i = low; // 指向左半分割槽陣列的指標 int j = dividedIndex + 1; // 指向右半分割槽陣列的指標 int auxPtr = 0; // 指向輔助區陣列的指標 // 合併兩個有序陣列:array[low..dividedIndex]與array[dividedIndex+1..high]。 while (i <= dividedIndex && j <= high) { // 將兩個有序的數組合並,排序到輔助陣列auxArray中 if (array[i] > array[j]) { // 左側陣列array[low..dividedIndex]中的array[i]大於右側陣列array[dividedIndex+1..high]中的array[j] auxArray[auxPtr++] = array[j++]; } else { auxArray[auxPtr++] = array[i++]; } } // 如果array[low..dividedIndex].length>array[dividedIndex+1..high].length,經過上面合併 // array[low..dividedIndex]沒有合併完,則直接將array[low..dividedIndex]中沒有合併的元素複製到輔助陣列auxArray中去 while (i <= dividedIndex) { auxArray[auxPtr++] = array[i++]; } // 如果array[low..dividedIndex].length<array[dividedIndex+1..high].length,經過上面合併 // array[dividedIndex+1..high]沒有合併完,則直接將array[dividedIndex+1..high]中沒有合併的元素複製到輔助陣列auxArray中去 while (j <= high) { auxArray[auxPtr++] = array[j++]; } // 最後把輔助陣列auxArray的元素複製到原來的陣列中去,歸併排序結束 for (auxPtr = 0, i = low; i <= high; i++, auxPtr++) { array[i] = auxArray[auxPtr]; } } }

排序過程

假設待排序陣列為array = {94,12,34,76,26,9,0,37,55,76,37,5,68,83,90,37,12,65,76,49},陣列大小為20,我們以該陣列為例,執行歸併排序的具體過程,如下所示:

[94,12,34,76,26,9,0,37,55,76,    37,5,68,83,90,37,12,65,76,49]
            [94,12,34,76,26,    9,0,37,55,76]
            [94,12,34,    76,26]
            [94,12,    34]
            [94,    12]
            {12,    94}
            {12,34,    94}
            [76,    26]
            {26,    76}
            {12,26,34,    76,94}
            [9,0,37,    55,76]
            [9,0,    37]
            [9,    0]
            {0,    9}
            {0,9,    37}
            [55,    76]
            {55,    76}
            {0,9,37,    55,76}
            {0,9,12,26,34,    37,55,76,76,94}
            [37,5,68,83,90,    37,12,65,76,49]
            [37,5,68,    83,90]
            [37,5,    68]
            [37,    5]
            {5,    37}
            {5,37,    68}
            [83,    90]
            {83,    90}
            {5,37,68,    83,90}
            [37,12,65,    76,49]
            [37,12,    65]
            [37,    12 ]
            {12,    37 }
            {12,37,    65 }
            [76,    49 ]
            {49,    76}
            {12,37,49,    65,76}
            {5,12,37,37,49,    65,68,76,83,90}
            {0,5,9,12,12,26,34,37,37,37,    49,55,65,68,76,76,76,83,90,94}

上面示例的排序過程中,方括號表示“分解”操作過程中,將原始陣列進行遞迴分解,直到不能再繼續分割為止;花括號表示“歸併”的過程,將上一步分解後的陣列進行歸併排序。因為採用遞迴分治的策略,所以從上面的排序過程可以看到,“分解”和“歸併”交叉出現。

演算法分析

  • 時間複雜度

對長度為n的檔案,需進行FLOOR(logn) 趟二路歸併,每趟歸併的時間為O(n),故其時間複雜度無論是在最好情況下還是在最壞情況下均是O(nlgn)。

  • 空間複雜度

需要一個輔助向量來暫存兩有序子檔案歸併的結果,故其輔助空間複雜度為O(n),顯然它不是就地排序。

  • 排序穩定性

歸併排序是一種穩定的排序。

轉載原地址:http://shiyanjun.cn/archives/820.html