歸併排序
(MERGE-SORT)是建立在歸併操作上的一種有效的排序算法,該算法是採用分治法(Divide and Conquer)的一個非常典型的應用。
- 分治法
可以通俗的解釋爲:把一片領土分解,分解爲若干塊小部分,然後一塊塊地佔領征服,被分解的可以是不同的政治派別或是其他什麼,然後讓他們彼此異化。
分治法的精髓:
分–將問題分解爲規模更小的子問題;
治–將這些規模更小的子問題逐個擊破;
合–將已解決的子問題合併,最終得出“母”問題的解;
歸併排序
我們經過看這個過程圖來了解歸併排序。
- 首先,是進行歸併的“分”,即__mergeSort(T arr[], int l, int r),將整個數組分成左右兩部分。
int mid = (l + r) / 2; //arr[l...r]
再將左右兩部分分別分成左右兩部分,直到分成單個元素。
- 第二步,是進行歸併的“並”,即__merge(T arr[], int l, int mid, int r),遞歸的進行一步步向上的左右兩部分的排序,也就是將arr[l…mid]和arr[mid+1…r]兩部分進行歸併。
在這裏要開闢一個輔助空間,即
T *aux = new T[r - l + 1];
將每次的兩部分排序過程在aux空間中進行。
C++完整代碼如下:
//歸併排序
// 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
template<typename T>
void __merge(T arr[], int l, int mid, int r)
{
// 經測試,傳遞aux數組的性能效果並不好
T *aux = new T[r - l + 1];
for (int i = l; i <= r; i++)
aux[i - l] = arr[i];
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++;
}
}
delete[] aux;
}
// 遞歸使用歸併排序,對arr[l...r]的範圍進行排序
template<typename T>
void __mergeSort(T arr[], int l, int r)
{
if (r - l <= 15) //這裏的優化是當左右兩部分分成小於等於16個時,用插入排序更快
{
insertionSort(arr, l, r);
return;
}
int mid = (l + r) / 2; //arr[l...r]
__mergeSort(arr, l, mid);
__mergeSort(arr, mid + 1, r);
if (arr[mid] > arr[mid + 1]) //這裏是個小優化,在左部分的最後一個元素小於右部分第一個元素時即這個子數組已有序
{
__merge(arr, l, mid, r);
}
}
template<typename T>
void mergeSort(T arr[], int n)
{
__mergeSort(arr, 0, n - 1);
}
性能測試
算法分析
(1)穩定性
歸併排序是一種穩定的排序。
(2)存儲結構要求
可用順序存儲結構。也易於在鏈表上實現。
(3)時間複雜度
對長度爲n的文件,需進行 趟二路歸併,每趟歸併的時間爲O(n),故其時間複雜度無論是在最好情況下還是在最壞情況下均是O(nlgn)。
測試1(與插入排序相比)
int n = 100000;
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);
SortTestHelper::testSort("Insertion Sort", insertionSort, arr1, n);
SortTestHelper::testSort("Merge Sort", mergeSort, arr2, n);
測試數據爲無序的10萬隨機數的數組。
結果如下:
測試2
int swapTimes = 100;
cout << "Test for Random Nearly Ordered Array, size = " << n << ", swap time = " << swapTimes << endl;
arr1 = SortTestHelper::generateNearlyOrderedArray(n, swapTimes);
arr2 = SortTestHelper::copyIntArray(arr1, n);
SortTestHelper::testSort("Insertion Sort", insertionSort, arr2, n);
SortTestHelper::testSort("Merge Sort", mergeSort, arr2, n);
10萬個基本有序的數組測試
結果如下:
測試3
cout << "Test for Random Array, size = " << n << ", random range [0,10]" << endl;
arr1 = SortTestHelper::generateRandomArray(n, 0, 10);
arr2 = SortTestHelper::copyIntArray(arr1, n);
SortTestHelper::testSort("Insertion Sort", insertionSort, arr2, n);
SortTestHelper::testSort("Merge Sort", mergeSort, arr2, n);
10萬個含有大量重複元素的數組
結果如下:
結果很明顯啦