排序算法-4-归并排序

归并排序

前面讲了插入排序选择排序冒泡排序,下面说一种不太容易想到的排序方法:归并排序。

原理

归并排序,是用分治法的一个典型的例子。分治法就是把一个大问题分成几个小问题,分而治之。那归并排序的原理就是把一个要排序的数组分成两个子数组,分别排序那两个子数组,然后归并成一个数组。而那两个子数组排序的时候同样可以用归并排序再分成两个子数组,分别排序后再合并。以此类推,可以一直分到分无可分,即数组中只有一个元素为止。

由上面的分析可见,切分子数组是很容易的,不需要什么技巧,一般来说,对半分就可以了。而归并排序的技巧在于归并的过程。

归并的过程,需要每次找出两个子数组中最小的一个元素,将其移动到结果数组中。因为两个子数组已经是排好序的,所以只需要比较两个子数组中最小的元素就可以了。一旦一个子数组中的元素都已移动到结果数组中,就不需要继续比较了,直接移动另一个子数组到结果数组中就行了。

实现

按照这个原理我们来用代码实现。同样的问题又来了:是否可以像插入排序选择排序冒泡排序一样,也不需要另外开辟空间,直接在原址上排序呢?这次的答案是不行。因为在归并的过程中我们需要额外的空间来存储结果数组。

下面就是用C语言实现的代码。分成三个函数来实现。

  • 要排序的数组a有n个元素。
  • merge_sort 函数进行封装,调用 merge_sort_。
  • merge_sort_ 进行子数组的切分,将数组a[p...r]切分成a[p...q]和a[q+1...r]两个子数组,然后调用 merge 进行归并。
  • merge 中先把要归并的两个已排好序的数组复制到 L 和 R 中,然后每次找出 L 和 R 中最小的一个元素放入 a 中。
void merge_sort(int a[], int n)
{
    if(n<=0)
        return;

    merge_sort_(a, 0, n-1);
}

void merge_sort_(int a[], int p, int r)
{
    if(p<r) {
        int q=(p+r)/2;
        merge_sort_(a, p, q);
        merge_sort_(a, q+1, r);
        merge(a, p, q, r);
    }
}

void merge(int a[], int p, int q, int r)
{
    int n1=q-p+1;
    int n2=r-q;
    int *L=malloc(n1*sizeof(int));
    int *R=malloc(n2*sizeof(int));
    for(int i=0;i<n1;i++) {
        L[i]=a[p+i];
    }
    for(int i=0;i<n2;i++) {
        R[i]=a[q+1+i];
    }
    int i=0; // index of L
    int j=0; // index of R
    int k=p; // index of a
    while(i<n1 && j<n2) {
        if(L[i]<=R[j]) {
            a[k]=L[i];
            i++;
        } else {
            a[k]=R[j];
            j++;
        }
        k++;
    }
    // copy the remaining to a
    if(i==n1) {
        for(int jj=0;jj<n2-j;jj++) {
            a[k+jj]=R[j+jj];
        }
    } else {
        for(int ii=0;ii<n1-i;ii++) {
            a[k+ii]=L[i+ii];
        }
    }
}

为了验证此函数的效果,加上了如下辅助代码,对3个数组进行排序,运行结果在最后,可见排序成功。

#include <stdio.h>
#include <stdlib.h>

#define SIZE_ARRAY_1 5
#define SIZE_ARRAY_2 6
#define SIZE_ARRAY_3 20

void merge_sort(int a[], int n);
void show_array(int a[], int n);

void main()
{
    int array1[SIZE_ARRAY_1]={1,4,2,-9,0};
    int array2[SIZE_ARRAY_2]={10,5,2,1,9,2};
    int array3[SIZE_ARRAY_3];

    for(int i=0; i<SIZE_ARRAY_3; i++) {
        array3[i] = (int)((40.0*rand())/(RAND_MAX+1.0)-20);
    }

    printf("Before sort, ");
    show_array(array1, SIZE_ARRAY_1);
    merge_sort(array1, SIZE_ARRAY_1);
    printf("After sort, ");
    show_array(array1, SIZE_ARRAY_1);

    printf("Before sort, ");
    show_array(array2, SIZE_ARRAY_2);
    merge_sort(array2, SIZE_ARRAY_2);
    printf("After sort, ");
    show_array(array2, SIZE_ARRAY_2);

    printf("Before sort, ");
    show_array(array3, SIZE_ARRAY_3);
    merge_sort(array3, SIZE_ARRAY_3);
    printf("After sort, ");
    show_array(array3, SIZE_ARRAY_3);
}

void show_array(int a[], int n)
{
    if(n>0)
        printf("This array has %d items: ", n);
    else
        printf("Error: array size should bigger than zero.\n");

    for(int i=0; i<n; i++) {
        printf("%d ", a[i]);
    }
    printf("\n");
}

运行结果:

Before sort, This array has 5 items: 1 4 2 -9 0
After sort, This array has 5 items: -9 0 1 2 4
Before sort, This array has 6 items: 10 5 2 1 9 2
After sort, This array has 6 items: 1 2 2 5 9 10
Before sort, This array has 20 items: 13 -4 11 11 16 -12 -6 10 -8 2 0 5 -5 0 18 16 5 8 -14 4
After sort, This array has 20 items: -14 -12 -8 -6 -5 -4 0 0 2 4 5 5 8 10 11 11 13 16 16 18

分析

时间复杂度

从代码可见,merge的过程是一层循环,为 n 的量级。而merge的次数是 log n,所以归并排序的时间复杂度为 O(n log n)。

空间复杂度

因为归并排序需要额外的空间来存储结果数组,结果数组为 n 的量级,所以空间复杂度是 O(n)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值