逆序数就是指比如:数组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; }