归并排序
(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万个含有大量重复元素的数组
结果如下:
结果很明显啦