逆序数的几种求法

来源:互联网 发布:解说词配音软件 编辑:程序博客网 时间:2024/05/17 09:12

逆序数就是指比如:数组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中已有多少个数 就是逆序数}elsea1[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];   // 相等的处理}elsea[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];   // 相等的处理}elsea[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 0
原创粉丝点击