O(n*logn)級別的演算法之一(歸併排序及其優化)
原理:
設兩個有序的子序列(相當於輸入序列)放在同一序列中相鄰的位置上:array[low..m],array[m + 1..high],先將它們合併到一個區域性的暫存序列 temp (相當於輸出序列)中,待合併完成後將 temp 複製回 array[low..high]中,從而完成排序。
在具體的合併過程中,設定 i,j 和 p 三個指標,其初值分別指向這三個記錄區的起始位置。合併時依次比較 array[i] 和 array[j] 的關鍵字,取關鍵字較小(或較大)的記錄複製到 temp[p] 中,然後將被複制記錄的指標 i 或 j 加 1,以及指向複製位置的指標 p加 1。重複這一過程直至兩個輸入的子序列有一個已全部複製完畢(不妨稱其為空),此時將另一非空的子序列中剩餘記錄依次複製到 array 中即可
備註:圖片原理來源:https://blog.csdn.net/yinjiabin/article/details/8265827/
待排序列(14,12,15,13,11,16)
假設我們有一個沒有排好序的序列,那麼首先我們使用分割的辦法將這個序列分割成一個個已經排好序的子序列。然後再利用歸併的方法將一個個的子序列合併成排序好的序列。分割和歸併的過程可以看下面的圖例。
先"分割"再"合併"
從上圖可以看出,我們首先把一個未排序的序列從中間分割成2部分,再把2部分分成4部分,依次分割下去,直到分割成一個一個的資料,再把這些資料兩兩歸併到一起,使之有序,不停的歸併,最後成為一個排好序的序列。
測試用例:
#ifndef INC_02_MERGE_SORT_SORTTESTHELPER_H #define INC_02_MERGE_SORT_SORTTESTHELPER_H #include <iostream> #include <algorithm> #include <string> #include <ctime> #include <cassert> using namespace std; namespace SortTestHelper { // 生成有n個元素的隨機陣列,每個元素的隨機範圍為[rangeL, rangeR] int *generateRandomArray(int n, int range_l, int range_r) { int *arr = new int[n]; srand(time(NULL)); for (int i = 0; i < n; i++) arr[i] = rand() % (range_r - range_l + 1) + range_l; return arr; } // 生成一個近乎有序的陣列 // 首先生成一個含有[0...n-1]的完全有序陣列, 之後隨機交換swapTimes對資料 // swapTimes定義了陣列的無序程度 int *generateNearlyOrderedArray(int n, int swapTimes){ int *arr = new int[n]; for(int i = 0 ; i < n ; i ++ ) arr[i] = i; srand(time(NULL)); for( int i = 0 ; i < swapTimes ; i ++ ){ int posx = rand()%n; int posy = rand()%n; swap( arr[posx] , arr[posy] ); } return arr; } // 拷貝整型陣列a中的所有元素到一個新的陣列, 並返回新的陣列 int *copyIntArray(int a[], int n){ int *arr = new int[n]; //* 在VS中, copy函式被認為是不安全的, 請大家手動寫一遍for迴圈:) copy(a, a+n, arr); return arr; } // 列印arr陣列的所有內容 template<typename T> void printArray(T arr[], int n) { for (int i = 0; i < n; i++) cout << arr[i] << " "; cout << endl; return; } // 判斷arr陣列是否有序 template<typename T> bool isSorted(T arr[], int n) { for (int i = 0; i < n - 1; i++) if (arr[i] > arr[i + 1]) return false; return true; } // 測試sort排序演算法排序arr陣列所得到結果的正確性和演算法執行時間 template<typename T> void testSort(const string &sortName, void (*sort)(T[], int), T arr[], int n) { clock_t startTime = clock(); sort(arr, n); clock_t endTime = clock(); cout << sortName << " : " << double(endTime - startTime) / CLOCKS_PER_SEC << " s"<<endl; assert(isSorted(arr, n)); return; } }; #endif //INC_02_MERGE_SORT_SORTTESTHELPER_H
插入排序(因為等下優化時需要用到):
#ifndef INC_03_MERGE_SORT_ADVANCE_INSERTIONSORT_H #define INC_03_MERGE_SORT_ADVANCE_INSERTIONSORT_H #include <iostream> #include <algorithm> using namespace std; template<typename T> void insertionSort(T arr[], int n){ for( int i = 1 ; i < n ; i ++ ) { T e = arr[i]; int j; for (j = i; j > 0 && arr[j-1] > e; j--) arr[j] = arr[j-1]; arr[j] = e; } return; } // 對arr[l...r]範圍的陣列進行插入排序 template<typename T> void insertionSort(T arr[], int l, int r){ for( int i = l+1 ; i <= r ; i ++ ) { T e = arr[i]; int j; for (j = i; j > l && arr[j-1] > e; j--) arr[j] = arr[j-1]; arr[j] = e; } return; } #endif //INC_03_MERGE_SORT_ADVANCE_INSERTIONSORT_H
一般歸併排序:
#ifndef INC_03_MERGE_SORT_ADVANCE_MERGESORT_H #define INC_03_MERGE_SORT_ADVANCE_MERGESORT_H #include <iostream> using namespace std; // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併 template<typenameT> void __merge(T arr[], int l, int mid, int r){ T aux[r-l+1]; for( int i = l ; i <= r; i ++ ) aux[i-l] = arr[i]; // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1 int i = l, j = mid+1; for( int k = l ; k <= r; k ++ ){ if( i > mid ){// 如果左半部分元素已經全部處理完畢 arr[k] = aux[j-l]; j ++; } else if( j > r ){// 如果右半部分元素已經全部處理完畢 arr[k] = aux[i-l]; i ++; } else if( aux[i-l] < aux[j-l] ) {// 左半部分所指元素 < 右半部分所指元素 arr[k] = aux[i-l]; i ++; } else{// 左半部分所指元素 >= 右半部分所指元素 arr[k] = aux[j-l]; j ++; } } } // 遞迴使用歸併排序,對arr[l...r]的範圍進行排序 template<typename T> void __mergeSort(T arr[], int l, int r){ if( l >= r ) return; int mid = (l+r)/2; __mergeSort(arr, l, mid); __mergeSort(arr, mid+1, r); __merge(arr, l, mid, r); } template<typename T> void mergeSort(T arr[], int n){ __mergeSort( arr , 0 , n-1 ); } #endif //INC_03_MERGE_SORT_ADVANCE_MERGESORT_H
優化後的歸併排序:
#include <iostream> #include "SortTestHelper.h" #include "InsertionSort.h" #include "MergeSort.h" using namespace std; // 使用優化的歸併排序演算法, 對arr[l...r]的範圍進行排序 template<typename T> void __mergeSort2(T arr[], int l, int r){ // 優化2: 對於小規模陣列, 使用插入排序 if( r - l <= 15 ){ insertionSort(arr, l, r); return; } int mid = (l+r)/2; __mergeSort2(arr, l, mid); __mergeSort2(arr, mid+1, r); // 優化1: 對於arr[mid] <= arr[mid+1]的情況,不進行merge // 對於近乎有序的陣列非常有效,但是對於一般情況,有一定的效能損失 if( arr[mid] > arr[mid+1] ) __merge(arr, l, mid, r); } template<typename T> void mergeSort2(T arr[], int n){ __mergeSort2( arr , 0 , n-1 ); } int main() { int n = 50000; // 測試1 一般性測試 cout<<"Test for random array, size = "<<n<<", random range [0, "<<n<<"]"<<endl; int* arr1 = SortTestHelper::generateRandomArray(n,0,n); int* arr2 = SortTestHelper::copyIntArray(arr1, n); int* arr3 = SortTestHelper::copyIntArray(arr1, n); SortTestHelper::testSort("Insertion Sort", insertionSort, arr1, n); SortTestHelper::testSort("Merge Sort",mergeSort,arr2, n); SortTestHelper::testSort("Merge Sort 2",mergeSort2,arr3, n); delete[] arr1; delete[] arr2; delete[] arr3; cout<<endl; // 測試2 測試近乎有序的陣列 int swapTimes = 10; assert( swapTimes >= 0 ); cout<<"Test for nearly ordered array, size = "<<n<<", swap time = "<<swapTimes<<endl; arr1 = SortTestHelper::generateNearlyOrderedArray(n,swapTimes); arr2 = SortTestHelper::copyIntArray(arr1, n); arr3 = SortTestHelper::copyIntArray(arr1, n); SortTestHelper::testSort("Insertion Sort", insertionSort, arr1, n); SortTestHelper::testSort("Merge Sort",mergeSort,arr2, n); SortTestHelper::testSort("Merge Sort 2",mergeSort2,arr3, n); delete[] arr1; delete[] arr2; delete[] arr3; return 0; }
測試結果: