逆序对
定义:对于一个包含N个非负整数的数组A[1..n],如果有i < j,且A[ i ]>A[ j ],则称(A[ i] ,A[ j] )为数组A中的一个逆序对。
例如,数组(3,1,4,5,2)的逆序对有(3,1),(3,2),(4,2),(5,2),共4个。
归并排序
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归并操作
归并操作(merge),也叫归并算法,指的是将两个顺序序列合并成一个顺序序列的方法。
如 设有数列{6,202,100,301,38,8,1}
初始状态:6,202,100,301,38,8,1
第一次归并后:{6,202},{100,301},{8,38},{1},比较次数:3;
第二次归并后:{6,100,202,301},{1,8,38},比较次数:4;
第三次归并后:{1,6,8,38,100,202,301},比较次数:4;
总的比较次数为:3+4+4=11;
逆序数为14;
算法描述
归并操作的工作原理如下:
第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置
第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针超出序列尾
将另一序列剩下的所有元素直接复制到合并序列尾
比较
归并排序是稳定的排序.即相等的元素的顺序不会改变.如输入记录 1(1) 3(2) 2(3) 2(4) 5(5) (括号中是记录的关键字)时输出的 1(1) 2(3) 2(4) 3(2) 5(5) 中的2 和 2 是按输入的顺序.这对要排序数据包含多个信息而要按其中的某一个信息排序,要求其它信息尽量按输入的顺序排列时很重要。归并排序的比较次数小于快速排序的比较次数,移动次数一般多于快速排序的移动次数。
用途
速度仅次于快速排序,为稳定排序算法,一般用于对总体无序,但是各子项相对有序的数列。
(2)求逆序对数
具体思路是,在归并的过程中计算每个小区间的逆序对数,进而计算出大区间的逆序对数(也可以用树状数组来求解)
求逆序对(的个数)
1.两重循环进行枚举。该算法的时间复杂度为O(n^2)。
int count_inversion(int *a, int N)
{
int count = 0;
int i, j;
for(i=0; i<N-1; i++)
for(j=i+1; j<N; j++)
if(a[i]>a[j])
count++;
return count;
}
2.利用归并排序的思想求解逆序对的个数,这是解决该问题的一种较为高效的算法。该算法的时间复杂度为O(nlogn)。
#include<iostream>
#include<cstdio>
using namespace std;
int n,a[2000001],i,c[2000001];
long long ans;
void x(int l,int r)
{
int mid=(l+r)/2,i,j,tmp;
if(r>l)
{
x(l,mid);
x(mid+1,r);
tmp=l;
for(i=l,j=mid+1;i<=mid&&j<=r;)
{
if(a[i]>a[j])
{
c[tmp++]=a[j++];
ans+=mid-i+1;
}
else c[tmp++]=a[i++];
}
if(i<=mid) for(;i<=mid;) c[tmp++]=a[i++];
if(j<=r) for(;j<=r;) c[tmp++]=a[j++];
for(i=l;i<=r;i++) a[i]=c[i];
}
}
int main()
{
cin>>n;
for(i=1;i<=n;i++) scanf("%d",&a[i]);
x(1,n);
cout<<ans;
}
样例输入
4 2 4 3 1
样例输出
4