「算法原理与实现」归并排序

一、分治模式

许多有用的算法在结构上是递归的:为了解决一个给定的问题,算法一次或多次递归地调用其自身以解决紧密相关的若干子问题。这些算法典型地遵循分治法的思想:将原问题分解为几个规模较小但类似于原问题的子问题。递归的求解这些问题,然后再合并这些子问题的解来建立原问题的解。

分治模式在每层递归都有三个步骤:

分解原问题为若干子问题,这些子问题是原问题的规模较小的实例。

解决这些子问题,递归地求解各子问题。然而,子问题的规模足够小,则直接求解。

合并这些子问题的解成原问题的解。

归并排序算法完全遵循分治模式。

分解:分解待排序的n各元素的序列各成n/2个元素的两个子列。

解决:使用归并排序递归地排序两个子序列。

合并:合并两个已排序的子列以产生已排序的答案。

归并算法中合并过程的伪代码如下:

MERGE(A,p,q,r)
1   n1 = q - p + 1
2   n2 = r - q
3   let L[1..n1 + 1] and R[1..n2 + 1] be new arrays
4   for i = 1 to n1
5       L[i] = A[p + i -1]
6   for j = 1 to n2
7       R[j] = A[q + j]
8   L[n1 + 1] = ∞
9   R[n2 + 1] = ∞
10  i = 1
11  j = 1
12  for k = p to r
13      if L[i] <= R[j]
14          A[k] = L[i]
15          i = i + 1
16      else 
17          A[k] = R[j]
18          j = j + 1

第1行计算子数组A[p..q]的长度你n1。 第2行计算子数组A[q+1..r]的长度n2。 在第3行,我们创建长度分别为n1+1,n2+1的数组L和R,每个数组中额外的位置保存哨兵。 第4~5行的for循环将子数组A[p..q]复制到L[1..n1]。 第6~7行的for循环将子数组A[q+1..r]复制到R[1..n2]。 第8~9行将哨兵放在数组L和R的末尾。 第10~18行在下图中,通过维持以下循环不变式,执行r-p+1个基本操作。

在开始第12~17行for循环的每次迭代时,子数组A[p..k-1]按从小到大的顺序包含L[1..n1+1]和R[1..n2+1]中的k-p个最小元素。进而L[i]和R[j]是各自所在数组中未被复制回数组A的最小元素。

归并排序证明循环不变式成立 
初始化:循环第一次迭代之前,有k=p,所以子数组A[p..k-1]为空。这个空的子数组包含L和R的k-p = 0个最小元素。又因为i=j=1,所以L[i]和R[j]都是各自所在数组中未被复制回数组A的最小元素。

保持:为了理解每次迭代都维持循环不变式,首先假设L[i]<=R[j]。这时,L[i]是未被复制回数组A的最小元素,子数组A[p..k]将包含k-p+1个最小元素。增加k的值和i的值后,为下次迭代重新建立了该循环不变式。反之,若L[i]>=R[j],则第17~18行执行适当的操作来维持该循环不变式。

终止:终止时k = r +1。根据循环不变式,子数组A[p..k-1]就是A[p..r]且按从小到大的顺序包含L[1...n1+1]和R[1..n2+1]中的k-p = r - p +1个最小元素。数组L和R一起包含n1+n2+2 = r-p+3个元素。除两个最大的元素以外,其他所有元素都已被复制回数组A,这两个最大的元素就是哨兵。

归并排序伪代码

1 MERGE-SROT(A,p,r)
2   if p < r
3       q = (p + r)/2
4       MERGE-SORT(A,p,q)
5       MERGE-SORT(A,q + 1,r)
6       MERGE(A,p,q,r)

归并排序实例图

对示例A = <5,2,4,7,1,3,2,6>进行归并排序

归并排序的时间复杂度为O(nlgn)。

因为递归树是lgn + 1层,所以总的代价为cnlgn + cn。

二、归并排序的具体实现过程

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

void merge(int intArray[], int begin, int mid, int end)
{
    int n1 = mid - begin + 1;
    int n2 = end - mid;
    int i, j, k;

    int *L = (int *)malloc(sizeof(int) * n1);
    int *R = (int *)malloc(sizeof(int) * n2);

    /*利用goto语句实现异常处理
    当L或R未能正常分配时
    将直接跳转至程序末尾,后续程序不再执行
    */
    if (L == NULL || R == NULL)
    {
        goto error;
    }

    for (i = 0; i < n1; i++)
        L[i] = intArray[begin + i];

    for (j = 0; j < n2; j++)
        R[j] = intArray[mid + 1 + j];

    i = j = 0;
    k = begin;

    while (i < n1 && j < n2)
    {
        if (L[i] < R[j])
        {
            intArray[k++] = L[i++];
        }
        else
        {
            intArray[k++] = R[j++];
        }
    }

    while (i < n1)
    {
        intArray[k++] = L[i++];
    }
    while (j < n2)
    {
        intArray[k++] = R[j++];
    }

    /*
    程序执行完毕后,在done处,进行资源释放
    */
    goto done;

error:
    printf("malloc has benn failed!\n");
done:
    if (L != NULL && R != NULL)
    {
        free(L);
        free(R);
    }
}

void merge_sort(int intArray[], int head, int tail)
{
    int mid;

    if (head  < tail)
    {
        mid = (head + tail) / 2;
        merge_sort(intArray, head, mid);
        merge_sort(intArray, mid + 1, tail);
        merge(intArray, head, mid, tail);
    }
}

int main(void)
{

    int a[8] = { 5, 2, 4, 7, 1, 3, 8, 6 };
    int i = 0;
    merge_sort(a, 0, 7);

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

    while (1);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值