关于逆序对的两种做法(归并排序+树状数组c++)

来源:互联网 发布:阿里巴巴淘宝城 编辑:程序博客网 时间:2024/05/16 14:55

这些天来测试的时候遇到了几次逆序对的题目,但没有太在意,已经跪了好几次了,所以今天打算学习总结一下逆序对的两种做法。


首先是归并排序


归并排序的模板

#include<iostream>using namespace std;int a[100000],temp[100000];void merge_i(int l,int mid,int r){int i=l,j=mid+1,k=l;//i是从l到mid枚举,j从mid+1到r枚举加入中介数组while(i<=mid&&j<=r){if (a[i]>a[j])temp[k++]=a[j++];elsetemp[k++]=a[i++];//按从小到大的顺序加入中介数组}while(i<=mid) temp[k++]=a[i++];//把左边区间剩余的加入中介数组while(j<=r) temp[k++]=a[j++];//把右边去见剩余的加入中介数组for(i=l;i<=r;i++)a[i]=temp[i];//返回排列顺序}void merge(int l,int r){if (l<r)//这个很重要 不然会运行错误{int mid=(l+r)>>1;//找中间位置merge(l,mid);//左区间排列merge(mid+1,r);//右区间排列merge_i(l,mid,r);//总的排列    }}int main(){int n;cin>>n;int i;for(i=1;i<=n;i++)cin>>a[i];merge(1,n);for(i=1;i<=n;i++) cout<<a[i]<<" ";return 0;}


接下来是关于归并排序对于逆序对的作用

转载于:http://blog.csdn.net/acdreamers/article/details/16849761(今天看的感觉还好理解,感谢ACdreamers大大)

我们可以这样考虑:


归并排序是将数列a[l,h]分成两半a[l,mid]和a[mid+1,h]分别进行归并排序,然后再将这两半合并起来。

在合并的过程中(设l<=i<=mid,mid+1<=j<=h),当a[i]<=a[j]时,并不产生逆序数;当a[i]>a[j]时,在

前半部分比a[i]大的数都比a[j]大,将a[j]放在a[i]前面的话,逆序数要加上mid+1-i。因此,可以在归并

序中的合并过程中计算逆序数.


接下来上代码


#include<iostream>using namespace std;int a[100000],temp[100000];long long ans=0;void merge_i(int l,int mid,int r){int i=l,j=mid+1,k=l;while(i<=mid&&j<=r){if (a[i]>a[j]){temp[k++]=a[j++];        ans+=mid-i+1;}elsetemp[k++]=a[i++];}while(i<=mid) temp[k++]=a[i++];while(j<=r) temp[k++]=a[j++];for(i=l;i<=r;i++)a[i]=temp[i];}void merge(int l,int r){if (l<r){int mid=(l+r)>>1;merge(l,mid);merge(mid+1,r);merge_i(l,mid,r);    }}int main(){int n;cin>>n;int i;for(i=1;i<=n;i++)cin>>a[i];merge(1,n);for(i=1;i<=n;i++) cout<<a[i]<<" ";cout<<endl;cout<<ans;return 0;}



接下来我们总结树状数组求逆序对

转载于http://blog.csdn.net/alongela/article/details/8142965(感谢Onlyan大大)

给定n个数,要求这些数构成的逆序对的个数。除了用归并排序来求逆序对个数,还可以使用树状数组来求解。

树状数组求解的思路:开一个能大小为这些数的最大值的树状数组。从头到尾读入这些数,每读入一个数就更新树状数组,查看它前面比它小的已出现过的有多少个数sum,然后用当前位置减去该sum,就可以得到当前数导致的逆序对数了。把所有的加起来就是总的逆序对数。

题目中的数都是独一无二的,这些数最大值不超过999999999,但n最大只是500000。如果采用上面的思想,必然会导致空间的巨大浪费,而且由于内存的限制,我们也不可能开辟这么大的数组。因此可以采用一种称为“离散化”的方式,把原始的数映射为1-n一共n个数,这样就只需要500000个int类型的空间。

离散化的方式:

struct Node

{

int val;

int pos;

};

Node node[500005];

int reflect[500005];

val存放原数组的元素,pos存放原始位置,即node[i].pos = i。

把这些结构体按照val的大小排序。

reflect数组存放离散化后的值,即reflect[node[i].pos] = i。

这样从头到尾读入reflect数组中的元素,即可以保持原来的大小关系,又可以节省大部分空间。


上代码

#include<iostream>#include<cstdio>#include<algorithm>using namespace std;struct tree{int v,x;}node[100000];int n,a[100000],c[100000],reflect[100000];bool cmp(tree a,tree b){return a.v<b.v;}int lowbit(int i){return i&(-i);//假如i是奇数,返回1,否则返回i的因数当中2的最大次方 例如 lowbit(8)返回8,lowbit(6)返回2  lowbit(20)返回4}void update(int i)//更新树状数组{while(i<=n){c[i]++;i+=lowbit(i);}}int sum(int i)//查看它前面比它小的已出现过的有多少个数sum{int t=0;while(i>0){t+=c[i];i-=lowbit(i);}return t;}int main(){int i;long long ans=0; cin>>n;for(i=1;i<=n;i++) {cin>>node[i].v;node[i].x=i;//记录输入位置}sort(node+1,node+1+n,cmp);//排序for(i=1;i<=n;i++) reflect[node[i].x]=i;//离散化,优化空间,并记录当前位置for(i=1;i<=n;i++){update(reflect[i]);ans+=i-sum(reflect[i]);//求逆序对的个数}cout<<ans;return 0;} 

0 0