逆序数的几种求法

逆序数就是指比如:数组A={2,4,3,5}那么<4,3>就是一个逆序数。

一:暴力匹配

对于数组A中的元素,i从0到n-1,j从i+1到n, 判断每一个是否为逆序数,时间复杂度O(N^2)。太简单了,没写代码了。。。。。

二:归并

归并排序能解决逆序数主要在于:比如归并A1={2,4,5}, A2={1,3},进行归并的时候,我们每次判断A1和A2中元素大小,这里有两种思路:(1)A1[i] <=A2[j],此时需要放入A1[i]了,我们可以计算已经放入的A2中的元素个数即为j-mid-1;思路(2)A1[i] <=A2[j],我们不管它,直接放入即可,但当A1[i] >A2[j],此时需要放入A2[j],我们来计算A1中还有多少个元素没有放mid – i+1,它们A2[j]所对应的逆序数个数。思路一对于归并算法有两处需要修改,思路二只需一处修改就可以了。时间复杂度为O(NlgN)

思路(1)代码:

void mergeSortInverseNumber(int a[], int p, int q, int a1[], long long int &number){
	if(p < q){
		int r = (p+q)/2;
		mergeSortInverseNumber(a, p, r, a1, number);
		mergeSortInverseNumber(a, r+1, q, a1, number);
		int i = p, j = r+1;
		int k = p;
		while(i <= r && j <= q){
			if(a[i] <= a[j]){
				a1[k++] = a[i++];
				number += j - r -1;    // 把a[i]放进来时 seq2中已有多少个数 就是逆序数
			}
			else
				a1[k++] = a[j++];
		}
		if(i > r){
			while(j <= q)
				a1[k++] = a[j++];
		}
		else{
			while(i <= r){
				a1[k++] = a[i++];
				number += j - r - 1;     这里也需要修改
			}
		}
		for(int i = p; i <=q; i++)
			a[i] = a1[i];
	}
}

思路(2)代码:

 

void mergeSortInverseNumber2(int a[], int p, int q, int a1[], long long int &number){
	if(p < q){
		int r = (p+q)/2;
		mergeSortInverseNumber(a, p, r, a1, number);
		mergeSortInverseNumber(a, r+1, q, a1, number);
		int i = p, j = r+1;
		int k = p;
		while(i <= r && j <= q){
			if(a[i] <= a[j])
				a1[k++] = a[i++];
				
			else{
				a1[k++] = a[j++];
				number += r - i + 1;    // 把a[j]放进来时 seq1中还有多少个元素没有放入 就是逆序数
			}
		}
		if(i > r){
			while(j <= q)
				a1[k++] = a[j++];
		}
		else{
			while(i <= r)
				a1[k++] = a[i++];
			
		}
		for(int i = p; i <=q; i++)
			a[i] = a1[i];
	}
}

三:树状数组

树状数组解法挺难理解的,我也还没有理解,也不需要理解。这里给出网上的一份代码:

 

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <string.h>
using namespace std;
 
const int maxn=500005;
int n;
int aa[maxn]; //离散化后的数组
int c[maxn];    //树状数组
 
struct Node{
   int v;
   int order;
}in[maxn];
 
int lowbit(int x)
{
    return x&(-x);
}
 
void update(int t,int value)
{
    int i;
    for(i=t;i<=n;i+=lowbit(i))
    {
        c[i]+=value;
    }
}
 
int getsum(int x)
{
    int i;
    int temp=0;
    for(i=x;i>=1;i-=lowbit(i))
    {
        temp+=c[i];
    }
    return temp;
}
 
bool cmp(Node a ,Node b)
{
    return a.v<b.v;
}
 
int main()
{
    int i,j;
    while(scanf("%d",&n)==1 && n)
    {
        //离散化
        for(i=1;i<=n;i++)
        {
            scanf("%d",&in[i].v);
            in[i].order=i;
        }
        sort(in+1,in+n+1,cmp);
        for(i=1;i<=n;i++) aa[in[i].order]=i;
        //树状数组求逆序
        memset(c,0,sizeof(c));
        long long ans=0;
        for(i=1;i<=n;i++)
        {
            update(aa[i],1);
            ans+=i-getsum(aa[i]);
        }
        cout<<ans<<endl;
    }
    return 0;
}

既然由前面一篇blog我们知道凡是树状数组能解决的问题,线段树都能解决,为什么不用线段树来解决呢?

 

四:线段树

线段树解决逆序数的思路是:设数组的大小为N,元素也都是1~N之间的元素,那么此时我们可以建立一棵[1,N]的线段树。遍历数组中的每个元素x时,我们需要查询线段树中[x+1,N]之间含有已插入元素的个数,并且更新线段树,将区间[x,x]及所有包含x的区间都加1。建立线段树为O(N),获得逆序数为O(lgN).

当然这里前提是元素大小都为1~N之间,那么如果不在这个区间怎么办呢,这时我们可以将离散的数据进行压缩。看以下代码:

// 离散化的数据进行压缩
struct arrayExtend{
	int val;
	int index;
};
int N;
	cin >> N;
	int *a = new int[N+1];
	//  通过一个结构体将数据进行压缩 压缩分范围为1~N之间 顺序保持不变
	arrayExtend *a1 = new arrayExtend[N+1];
	for(int i = 1; i <= N; i++){
		cin >> a1[i].val;
		a1[i].index = i;
	}
	sort(a1+1, a1+N+1, cmp);  // 排序
	a[a1[1].index] = 1;
	for(int i = 2; i <= N; i++){
		if(a1[i].val == a1[i-1].val){
			a[a1[i].index] = a[a1[i-1].index];   // 相等的处理
		}else
			a[a1[i].index] = i;   // 不相等的处理
	}

完整代码:

/*功能:线段树来求逆序数*/
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
#define MAXSIZE  100000
#define M 300000

// 离散化的数据进行压缩
struct arrayExtend{
	int val;
	int index;
};
bool cmp(arrayExtend ae1, arrayExtend ae2){
	return ae1.val < ae2.val;
}

// 线段树结点
struct node{
	int left, right, sum;
}t[M];



void build(int root, int start, int end){
	//  如果相等 则为叶子结点 返回
	if(start == end){
		t[root].sum = 0;
		t[root].left = start;
		t[root].right = end;
		return;
	}
	int mid = (start+end) >> 1;
	build(root*2+1, start, mid);   // 创造左子树
	build(root*2+2, mid+1, end);   // 创造右子树
	t[root].sum = 0;
	t[root].left = start;
	t[root].right = end;
}


long long int getSum(int root, int qstart, int end){
	if(qstart > end) return 0;
	// 如果查询区间大于待查区间 则返回其值
	if(t[root].left >= qstart && t[root].right <= end) return t[root].sum;
	int mid = (t[root].left + t[root].right) >> 1;
	if(qstart > mid) return getSum(root*2+2, qstart, end);
	else return getSum(root*2+1, qstart, end) + getSum(root*2+2, qstart, end);
}

// 值为index 此时将包含index的区间sum+1 表示该区间有多少个数
void update(int root, int index){
	if(t[root].left == t[root].right){
		t[root].sum += 1;
		return;
	}
	int mid = (t[root].left + t[root].right) >> 1;
	if(mid < index) update(root*2+2, index);
	else update(root*2+1, index);
	t[root].sum = t[root*2+1].sum + t[root*2+2].sum;   // 回溯
}



int main(){
	int N;
	cin >> N;
	int *a = new int[N+1];
	//  通过一个结构体将数据进行压缩 压缩分范围为1~N之间 顺序保持不变
	arrayExtend *a1 = new arrayExtend[N+1];
	for(int i = 1; i <= N; i++){
		cin >> a1[i].val;
		a1[i].index = i;
	}
	sort(a1+1, a1+N+1, cmp);  // 排序
	a[a1[1].index] = 1;
	for(int i = 2; i <= N; i++){
		if(a1[i].val == a1[i-1].val){
			a[a1[i].index] = a[a1[i-1].index];   // 相等的处理
		}else
			a[a1[i].index] = i;   // 不相等的处理
	}

	build(0, 1, N);
	long long int ans = 0;
	for(int i = 1; i<= N; i++){
		ans += getSum(0, a[i]+1, N);    //  输入a[i]  即寻找【a[i]+1,N】之间元素个数
		update(0, a[i]);         // 更新线段树
	}
	cout << ans << endl;

	delete []a1;
	delete []a;
	return 0;
}

注意存储一棵线段树所需要的结点树为满二叉树的结点个数为 2^(ceil(logN)+1)-1的个数。此外逆序数个数为long long 不然hihoCoder第三十九周只能得到80分。因为数据为10^5,逆序数个数可以达到5*10^9, 而int大小只有4*10^9多点。



 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值