归并排序 及拓展—逆序对
归并排序
时间复杂度
归并排序时间复杂度为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在连续的lmid上是有序的,在mid+1r上是有序的,要把合并的话
表达如下
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)