归并排序

来源:互联网 发布:数据库数据如何查找 编辑:程序博客网 时间:2024/06/05 11:21

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