排序如此美妙
众所周知,快速排序时间复杂度并不稳定,此外它也是一个不稳定的排序算法,那么有没有更好的,复杂度也为O(nlgn)的算法呢?
归并排序
运用归并排序,不仅能够实现绝对稳定的O(nlgn)时间复杂度,还能保证稳定性,何乐而不为。
但是归并排序的缺点是常数比快排略大,在随机数据下不如快排。可面对极限数据,快排也只能跪舔归排叫爹了。
归并排序的主要思想建立在合并有序表上:
现在有两个有序表a, b,如何将这两个有序表合并为一个有序表c,使得c也有序?
例如当a = {1, 3, 5, 7, 9}, b = {2, 4, 6, 8, 10},合并后c = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
很显然,这个过程是能够用贪心思想于O(n)的时间复杂度完成的。
如上图,我们分别在a, b两个线性表上设置两个指针i, j。
显然若a[i] < b[j],就可以将a[i]放入c,并将i后移一位,否则将b[j]放入c,并将j后移一位。若a, b长度不等,那么必然有一个多出来一部分数,这一部分数可以直接插入至c末尾。
这个过程显然是正确的,为什么呢?因为a, b都是有序的,我们插入a[i]且右移i的过程,就相当于把
int a[N], b[N], c[N];
void MergeArray()
{
//若a长度为n,b长度为m
int len = 0; //c长度一开始为0
int i = 1, j = 1; //一开始将指针设置在数列的开头
while (i <= n && j <= m) //长度较短的数列还没有放完
if (a[i] < b[j]) //a[i]较小,插a[i]进c
c[++len] = a[i++];
else //否则插b[j]进c
c[++len] = b[j++];
while (i <= n) //a数列有多余部分
c[++len] = a[i++]; //直接放入c的末尾
while (j <= m) //b数列有多余部分
c[++len] = b[j++]; //直接放入c的末尾
}
很显然时间复杂度是O(n)
放入归并排序中想,我们可以将数列中的一个数作为一个有序表(一个数不有序吗?),那么两个数就可以合并为一个有序表。对于数列a,如果其有序,那么a[1~1]和a[2~n]也必然有序,因此若我们要排序a数列,可以先排序a[1~1]和a[2~n],再用归并过程归并a[1~1]和a[2~n]。但是这个过程的时间复杂度为O(n^2),达不到预期效果。
那么如何归并,归并哪些数组,才能使时间复杂度更小?
思考前面的分法为什么会使得复杂度变成O(n^2),因为所分的两个部分长度不均等,如果我们将序列二分为a[1~mid]和a[mid+1~n],那么时间复杂度就变成了O(nlgn)了(纸上模拟一下就看的出来,我就不作详细证明了)。
这个分序列的过程是分治过程,因此使用递归解决。
C++代码:
#include <cstdio>
#include <cstdlib>
const int N = 200007;
int a[N];
int n;
void merge(int l, int r)
{
int mid = (l + r) >> 1; //得到中间点
int i = l, j = mid + 1, le = 0; //划分为a[l~mid]和a[mid+1~r]
int *p = (int*)malloc(sizeof(int) * (r - l + 10)); //开一个临时数组
//归并结果存至临时数组
while (i <= mid && j <= r)
if (a[i] < a[j])
p[++le] = a[i], i++;
else
p[++le] = a[j], j++;
while (i <= mid)
p[++le] = a[i], i++;
while (j <= r)
p[++le] = a[j], j++;
for (int i = l; i <= r; i++) //把临时数组的内容放回原数组
a[i] = p[i - l + 1];
free(p); //释放内存
}
int mergesort(int l, int r)
{
if (l < r) //不只有一个数
{
int mid = (l + r) >> 1; //求中间点
mergesort(l, mid); //排序左边
mergesort(mid + 1, r); //排序右边
}
merge(l, r); //合并两边
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", a + i);
mergesort(1, n);
for (int i = 1; i <= n; i++)
printf("%d\n", a[i]);
return 0;
}