逆序数的几种求法
来源:互联网 发布:解说词配音软件 编辑:程序博客网 时间: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多点。
- 逆序数的几种求法
- 逆序数的几种求法
- 逆序数的几种求法
- 逆序数的几种求法
- 逆序数的几种求法
- 逆序数的几种求法
- 逆序数的几种求法
- 逆序数的几种求法
- 逆序数的求法
- 逆序数的求法
- 逆序数的三种求法
- 组合数的几种求法
- 组合数的几种求法
- 逆序数求法
- 组合数的模的几种求法总结
- 求逆序数的几种方法
- 求逆序数的几种方法
- 求逆序数的几种做法
- android开发 - Widgets窗口小部件
- 用Java语言实现二叉树删除结点
- leetcode || 60、Permutation Sequence
- poj 2796 Feel Good
- C++易忽略点
- 逆序数的几种求法
- android开发 - SDCARD操作
- Step1 -- 确定主题与相关的内容
- 2. 第一个 PHP 页面
- 求最长连续相同的字母序列长度
- js对象属性值访问
- android开发 - 编程实现布局界面
- Android获取当前最前面的Activity
- (拓扑排序)poj2367,Genealogical tree