归并排序

1、引例

        在说明归并排序之前,我们先看看这样的一个问题:

给出两个序列Ln1,Rn2(Ln1,Rn2都为非递减序列)。现在问题是,让你将Ln1和Rn2合并为一个非递减序列。

         我们稍微一分析,容易写出下列代码:

void merge(int *L, int n1, int *R, int n2, int *arr){
    int i = 0, j = 0, k = 0;
    while(i < n1 && j < n2){
        if(L[i] < R[i]) arr[k++] = L[i++];
        else arr[k++] = R[j++];
    }
    while(i < n1) arr[k++] = L[i++];
    while(j < n2) arr[k++] = R[j++];
}

2、归并排序

        但是现在我们把问题改成:

给定一无序序列an(n为序列长度),现在让你将该序列变为非递减的序列

 

        显然,现在我们的问题就变为一个排序问题.通过上述的例子,我们不禁会联想到一种排序方法.如果序列an的[0, n/2)与[n/2, n)都排好序了,那么我们直接使用刚才例子的方法合并就可以了.那么接着我们就会思考,一个序列什么时候他的前一半和后一半都是排好序的.稍微一思考我们容易想到当序列长度为2的时候,其前一半和后一半自然就是有序的(因为他的前一半和后一半都各自只有一个元素).那么我们很自然的可以想到,把一个序列不断的分割直到他的长度为1时,我们就开始合并.于是我们就可以得出如下代码:

void merge(int *arr, int start, int mid, int end){
    //额外申请空间保存区间[start, mid)与[mid, end)的数
    int n1 = mid - start;
    int n2 = end - mid;
    int *L = new int[n1];
    int *R = new int[n2];
    int i = 0, j = 0, k = start;
    while(i < n1) L[i++] = arr[k++];
    while(j < n2) R[j++] = arr[k++];
    //将Ln1与Rn2合并
    i = j = 0, k = start;
    while(i < n1 && j < n2){
        if(L[i] < R[j]) arr[k++] = L[i++];
        else arr[k++] = R[j++];
    }
    while(i < n1) arr[k++] = L[i++];
    while(j < n2) arr[k++] = R[j++];
    //释放Ln1,Rn2的空间
    delete[] L;
    delete[] R;
    L = R = nullptr;
}

void merge_sort(int *arr, int start, int end){
    if(start+1 < end){  //区间至少有一个元素
        int mid = (start + end) >> 1; //区间中点,将区间一分为二
        merge_sort(arr, start, mid);  //对区间[start, mid)进行排序
        merge_sort(arr, mid, end);    //对区间[mid, end)进行排序
        merge(arr, start, mid, end);  //合并区间[start, mid)和[mid, end)
    }
}

        这样我们就实现了归并排序。那么我们来分析下其复杂度。首先时间复杂度,我们容易看出它的时间由递归的深度和每一层递归的宽度。由于每次一分为二,递归深度为logn,每一层都有n个元素,其宽度为n。故其时间复杂度为O(nlogn)。我们分析下他的空间复杂度,由于每次归并我们需要额外的空间来保存数据,故可得到归并排序的空间复杂度为O(n)。

3、归并排序求逆序对数

假设a[1...n]是有n个数的序列。若i < j且A[i] > A[j],则对偶(i, j)称为A的逆序对。现在问题是给定一个序列an,问其有多少对逆序对?

       看到这个问题我们可以想到,归并排序时,将序列an一分为二Ln1, Rn2。在非降序的排序中,如果L[i] <= R[j],显然没有逆序数,只有当L[i] > R[j]时产生逆序数。此时L[i+1...n1]里的元素均比R[j]大,而R[j]又在它们的后面.所以,此时的逆序对数:n1-i。

        我们来一道题练练手:POJ1804 Brainman。附上该题代码:

#include <cstdio>
#define MAXN (1000)
int inversion, a[MAXN];
void merge(int *arr, int start, int mid, int end){
    int n1 = mid - start;
    int n2 = end - mid;
    int *L = new int[n1];
    int *R = new int[n2];
    int i = 0, j = 0, k = start;
    while(i < n1) L[i++] = arr[k++];
    while(j < n2) R[j++] = arr[k++];
    i = j = 0, k = start;
    while(i < n1 && j < n2){
        if(L[i] > R[j]) arr[k++] = R[j++], inversion += n1 - i;//加上逆序对
        else arr[k++] = L[i++];
    }
    while(i < n1) arr[k++] = L[i++];
    while(j < n2) arr[k++] = R[j++];
    delete[] L;
    delete[] R;
    L = R = NULL;
}
void merge_sort(int *arr, int start, int end){
    if(start+1 < end){
        int mid = (start + end) >> 1;
        merge_sort(arr, start, mid);
        merge_sort(arr, mid, end);
        merge(arr, start, mid, end);
    }
}
int main(){
    int T, n;
    scanf("%d", &T);
    for(int nCase = 1; nCase <= T; nCase++){
        scanf("%d", &n);
        for(int i = 0; i < n; i++)
            scanf("%d", &a[i]);
        inversion = 0;
        merge_sort(a, 0, n);
        printf("Scenario #%d:\n%d\n\n", nCase, inversion);
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值