归并排序 及拓展—逆序对

归并排序

时间复杂度

归并排序时间复杂度为O(NlogN)
似乎和快速排序差不多,但在有些特定的场合下,归并排序却能起到快速排序达不到的效果(如一年的联赛题,瑞士轮)

思路及实现

归并排序分为两个步骤,分、合;
分 的过程我们用二分的思路实现;
合 的过程时间复杂度可达到O(n);

分:

进行分治:
假设当前处理的区间为l~r;
实现:
过程定义:void merge_sort(int l,int r)

merge_sort(l,l+r>>1);
merge_sort(l+r>>1+1,r);
合:

过程定义:void merge_group(int l,int r)

void merge_group(int l,int r)
{
    int i=l,mid=l+r>>1,j=mid+1;
    for(int k=l;k<=r;k++)
        if(j>r||i<=mid&&a[i]<a[j])
            b[k]=a[i++];
        else
            b[k]=a[j++];
}
理解的话就不用记了

但对于一小部分人,这是不是很难记

这就到了我们stl发挥功效的时候了

介绍inplace_merge函数(头文件#include<algorithm>)

举个例子,数组a在连续的l~mid上是有序的,在mid+1~r上是有序的,要把合并的话
表达如下

inplace_merge(a+l,a+mid+1,a+r+1);

最终代码:

#include<iostream>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
using namespace std;

inline int read()
{
    char chr=getchar();
    int f=1,ans=0;
    while(!isdigit(chr)) {if(chr=='-') f=-1;chr=getchar();}
    while(isdigit(chr))  {ans=ans*10;ans+=chr-'0';chr=getchar();}
    return ans*f;

}
int a[100],b[100],n;

void merge_group(int l,int r)//手打合并
{
    int i=l,mid=l+r>>1,j=mid+1;
    for(int k=l;k<=r;k++)
        if(j>r||i<=mid&&a[i]<a[j])
            b[k]=a[i++];
        else
            b[k]=a[j++];
    for(int k=l;k<=r;k++) 
        a[k]=b[k];
}

void merge_sort(int l,int r) 
{
    if(l<r)
    {
        int mid=l+r>>1;
        merge_sort(l,mid);
        merge_sort(mid+1,r);
        inplace_merge(a+l,a+mid+1,a+r+1);
//      merge_group(l,r);//手打合并
    }
    return;
}

int main()
{
    n=read();
    for(int i=1;i<=n;i++)
        cin>>a[i];
    merge_sort(1,n);
    for(int i=1;i<=n;i++)
        cout<<a[i]<<endl;
    return 0;
}

hmmmm然后是拓展——求逆序对个数

什么是逆序对呢?

给定一组数列若其中存在i<j而a[i]>a[j],那么这就是一组逆序对

看下面一组例子

5 4 2 6 3 1
其中逆序对有
5 4
5 2
5 3
5 1
4 2
4 3
4 1
2 1
6 3
6 1
3 1
共11组

朴素算法:O(N^2) //显然数据过大便无法接受

for(int i=1;i<n;i++)
    for(int j=i+1;j<=n;j++)
    if(a[i]>a[j]) ans++;

算法升级:O(n logn)
思路:归并排序

在归并排序的合并步骤中,假设将两个有序数组A[] 和有序数组B[] 和并为一个有序数组C[]。计算逆序对问题转换为计算逆序对(a,b)的问题,其中a来自A[], b来自B[]。当a < b的时候,不计数,当a>b的时候(a,b)就是逆序对,由于A[]是有序的,那么A[]中位于a之后的元素对于B[]中的元素b也形成了逆序对,于是对于逆序对(a,b),(假设A[]的起始下标为sa,结束下标为ea,a的下标为pos)实际上合并成C[]后会会产生ea-pos+1个逆序对。

(我觉得,这一块我自己可能不能讲得很清楚,所以……………以上内容摘自流动的城市的博客https://blog.csdn.net/Sugar_Z_/article/details/48213537)

好了,这时便不得不手打合并过程了

但在合并原程下加一丢丢改变就OK了

修改合并的过程,其他不变

void merge_group(int l,int r)
{
    int i=l,mid=l+r>>1,j=mid+1;
    for(int k=l;k<=r;k++)
        if(j>r||i<=mid&&a[i]<a[j])
            b[k]=a[i++];
        else
            b[k]=a[j++],ans+=mid-i+1;//改动
    for(int k=l;k<=r;k++) 
        a[k]=b[k];
}

附上练习题目Cow Photographs(Usaco2010Nov)

猜你喜欢

转载自blog.csdn.net/zheng_lw/article/details/81082597