合并排序算法

算法思想

合并排序算法是用分治策略实现对n个元素进行排序的算法。其基本思想是:将待排序元素分成大小大致相同的两个子集合,分别对两个子集合进行排序,最终将排好序的子集合合并成所要求的排好序的集合。合并算法可递归描述如下:

void MergeSort(int a[], int left, int right)
{
    if (left < right)
    { //至少还有两个元素
        int mid = (left + right) / 2;   //取中点
        MergeSort(a, left, mid);
        MergeSort(a, mid+1, right);
        Merge(a, b, left, mid, right);  //合并到数组b
        Copy(a, b, left, right);        //复制回数组a
    }
}

其中,算法Merge合并两个排好序的数组段到一个新的数组b中,然后由Copy将合并后的数组段再复制回数组a中。Merge和Copy都可在O(n)时间内完成,因此合并排序算法对n个元素进行排序,在最坏情况下所需的计算时间T(n)满足:

T(n)={O(1)2T(n/2)+O(n)n <= 1n > 1

解此递归方程可得T(n) = O(nlgn).由于排序问题的计算时间下界为

Ω(nlgn) ,所以合并排序算法是一个渐进最优算法。

实现代码

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

void MergeSort(int a[], int left, int right);
void Merge(int a[], int b[], int l, int m, int r);
void Copy(int a[], int b[], int s, int e);

int main()
{
    int i, n;
    printf("please enter array size:");
    scanf("%d", &n);
    int a[n];
    printf("please enter number:");
    for (i = 0; i < n; i++)
        scanf("%d", &a[i]);

    printf("array:\n");
    for (i = 0; i < n; ++i)
        printf("%4d", a[i]);
    printf("\n");

    MergeSort(a, 0, n-1);
    printf("sort array:\n");
    for (i = 0; i < n; ++i)
        printf("%4d", a[i]);
    printf("\n");

    return 0;
}

void MergeSort(int a[], int left, int right)
{ //合并排序
    if (left < right)
    { //至少还有两个元素
        int n = right - left + 1;       //b数组大小
        int b[n];   //下标从0开始
        int mid = (left + right) / 2;   //取中点
        MergeSort(a, left, mid);        //排序左边部分
        MergeSort(a, mid + 1, right);   //排序右边部分
        Merge(a, b, left, mid, right);  //合并到数组b
        Copy(a, b, left, right);        //复制回数组a
    }
}

void Merge(int a[], int b[], int l, int m, int r)
{ //把a数组排好序的左右两部分合并到b数组中
    int i, j, k;
    i = l;      //i指向a左边起始下标
    j = m + 1;  //j指向a右边起始下标
    k = 0;      //k指向b数组起始下标
    while (i <= m && j <= r)
    {
        if (a[i] < a[j])
            b[k++] = a[i++];
        else
            b[k++] = a[j++];
    }

    /*把数组剩余的部分复制到b中*/
    while (i <= m)
        b[k++] = a[i++];
    while (j <= r)
        b[k++] = a[j++];
}

void Copy(int a[], int b[], int s, int e)
{ //b数组复制到a数组
    int i, j;
    for (i = s, j= 0; i <= e; ++i, ++j)
        a[i] = b[j];
}

算法改进

对于算法MergeSort,还可以从分支策略的机制入手,容易消除算法中的递归。事实上,算法MergeSort的递归过程只是将待排序集合一分为二,直至待排序集合只剩下一个元素为止,然后不断合并两个排好序的数组段。按此机制,可以首先将数组a中相邻元素两两配对。用合并算法将他们排序,构成n/2组长度为2的排好序的子数组段,然后将他们排序成长度为4的排好序的子数组段,如此继续下去,直至整个数组排好序。
按此思想,消去递归后的合并排序算法可描述如下:

void MergeSort(int a[], int n)
{
    int b[n];
    int s = 1;
    while (s < n)
    {
        MergeSort(a, b, s, n);  //合并到数组b
        s += s;
        MergeSort(b, a, s, n);  //合并到数组a
        s += s;
    }
}

其中MergePass用于合并排好序的相邻数组段。具体的合并算法由Merge来实现。对于MergePass函数,合并排好序的相邻数组段。到最后,可能剩下元素不足2s个,对于大于s个元素和小于等于s个元素分别采用不同的处理方式。具体见函数定义。
对于函数Merge,与之前大致相同,只是数组b下标从l开始,而之前的Merge函数,下标从0开始。

改进后的代码

/* **合并排序——非递归版本 */
#include <stdio.h>
#include <stdlib.h>

void MergeSort(int a[], int n);
void MergePass(int a[], int b[], int s, int n);
void Merge(int a[], int b[], int l, int m, int r);

int main()
{
    int i, n;
    printf("please enter array size:");
    scanf("%d", &n);
    int a[n];
    printf("please enter number:");
    for (i = 0; i < n; i++)
        scanf("%d", &a[i]);

    printf("array:\n");
    for (i = 0; i < n; ++i)
        printf("%4d", a[i]);
    printf("\n");

    MergeSort(a, n);
    printf("sort array:\n");
    for (i = 0; i < n; ++i)
        printf("%4d", a[i]);
    printf("\n");

    return 0;
}

void MergeSort(int a[], int n)
{ //合并排序
    int b[n];
    int s = 1;
    while (s < n)
    {
        MergePass(a, b, s, n);  //合并到数组b
        s += s;
        MergePass(b, a, s, n);  //合并到数组a
        s += s;
    }
}

void MergePass(int a[], int b[], int s, int n)
{
    int i = 0, j;
    while (i <= n - 2 * s)
    { //max(i) 到 n-1的闭区间大小为2s
        Merge(a, b, i, i+s-1, i+2*s-1); //合并大小为s的相邻2段子数组
        i = i + 2*s;
    }

    //剩下的运算个数少于2s个
    if (i + s < n)
    { //至少还有s+1个元素
        Merge(a, b, i, i+s-1, n-1);
    }
    else
    { //最多剩下s个元素
        for (j = i; j < n; j++)
            b[j] = a[j];
    }
}

void Merge(int a[], int b[], int l, int m, int r)
{ //合并a[l:m]和a[m+1:r]到b[l:r]
    int i = l, j = m+1, k = l;
    while (i <= m && j <= r)
    {
        if (a[i] <= a[j])
            b[k++] = a[i++];
        else
            b[k++] = a[j++];
    }

    /*把数组剩余的部分复制到b中*/
    while (i <= m)
        b[k++] = a[i++];
    while (j <= r)
        b[k++] = a[j++];
}
点赞