归并排序的思路其实非常简单,就是把一个待排序的数组从中间进行拆分,一直到元素个数为1,然后进行两两合并,保证合并后的数组为有序数组。其本质思想就是分治,将大问题分成小问题,小问题解决了,大问题也就解决了。
具体的流程如下图所示:
在合并的时候,还有一个小技巧:
首先申请一个临时数组,然后使用两个游标i和j,分别指向左数组和右数组的第一个元素。如果array[i] >= array[j],则将array[i]放入临时数组,i++;否则,将array[j]放入临时数组,j++。接下来,将左数组或者右数组中剩余的元素放入临时数组中。最后将临时数组中的元素拷贝到原数组即可。
#include <iostream>
using namespace std;
const int N = 100010;
int arr[N], n;
void getAns(int arr[], int left, int right) {
if(left >= right) return;
int mid = left + ((right - left) >> 1);
getAns(arr, left, mid);
getAns(arr, mid + 1, right);
// 合并
int i = left, j = mid + 1, tmp[n], idx = 0;
while(i <= mid && j <= right) {
if(arr[i] <= arr[j])
tmp[idx++] = arr[i++];
else
tmp[idx++] = arr[j++];
}
while(i <= mid)
tmp[idx++] = arr[i++];
while(j <= right)
tmp[idx++] = arr[j++];
for(int x = left; x <= right; ++x)
arr[x] = tmp[x - left];
}
int main() {
cin >> n;
for(int i = 0; i < n; ++i) scanf("%d", &arr[i]);
getAns(arr, 0, n - 1);
for(int i = 0 ; i < n; ++i) printf("%d ", arr[i]);
printf("\n");
return 0;
}
这里,我们还需要关注三个问题:
-
归并排序是否是稳定的排序?
是的。因为当array[i] >= array[j]的时候,是将array[i]放入临时数组的。
-
归并排序是否是原地排序?
不是。因为每次回溯的时候,需要申请一个临时数组,所以其空间复杂度是O(n)的。
-
归并排序的时间复杂度是多少?
T(n) = 2 * T(n/2) + CT(n) = 2 * (2 * T(n/4) + C/2) + CT(n) = 2 * T(n/2) + C = 2 * (2 * T(n/4) + C/2) + C = 4 * T(n/4) + 2 * C = 4 * (2 * T(n/8) + C/4) + 2 * C = 8 * T(n/8) + 3 * C = 2^3 * T(n/2^3) + 3 * C = 2^k * T(n/2^k) + k * Cn/2^k = 1 k = lognT(n) = nT(1) + Clogn, C = n最好,最坏和平均都是O(nlogn)。