None

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/er111er/article/details/78987999

排序如此美妙


众所周知,快速排序时间复杂度并不稳定,此外它也是一个不稳定的排序算法,那么有没有更好的,复杂度也为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;
}

猜你喜欢

转载自blog.csdn.net/er111er/article/details/78987999
今日推荐