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

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


首先是归并排序


归并排序的模板

#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++];
		else
		temp[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;
		}
		else
		temp[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;
} 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值