求解逆序对数的几种方法

来源:互联网 发布:淘宝拍卖业务管理规范 编辑:程序博客网 时间:2024/05/17 05:02

求解逆序对数的几种方法:

设A为一个包含n个数字的有序集合(n>1),其中所有的数字均不相同。
如果存在两个正整数i,j,使得1i<jn,且A[i]>A[j],则(A[i],A[j])这个有序对称为A的一个逆序对。例如,数组(31452)的逆序对有(3,1),(3,2),(4,2),(5,2),共4个。

1. 两重循环来求解,时间复杂度为O(n2),大致代码如下:

int GetReverse(int* a, int n) {    int sum = 0;    for (int i = 0; i < n-1; ++i) {        for (int j = i+1; j < n; ++j) {            if (a[i] > a[j]) ++sum;        }    }    return sum;}

2. 树状数组求解,时间复杂度O(nlogn)

大致思想:
假设我们有个标记数组flag,长度为n;
先在n个数中找到最大的元素e1,其下表为p1,将flag[p1]置为1,统计p1之前的1的个数,即为与e1构成的逆序对数;
找到第二大元素e2,其下标为p2,将flag[p2]置为1,统计p2之前的1的个数,即为与e2构成的逆序对数;
以此类推,全部找完之后,统计的总个数即为序列中逆序对的总数。

拿开头的例子来说:
3 1 4 5 2,初始状态flag数组均为0(约定下标从1开始);
取最大数5,flag数组变为 0 0 0 1 0,统计下标4之前的1的个数为0,总逆序对数为0;
取第二大数4,flag数组变为 0 0 1 1 0,统计下标3之前的1的个数为0,总逆序对数为0;
取第三大数3, flag数组变为 1 0 1 1 0,统计下标1之前的1的个数为0,总逆序对数为0;
取2,flag数组变为 1 0 1 1 1,统计下标5之前的1的个数为3,总逆序对数为3;
取1,flag数组变为 1 1 1 1 1,统计下标2之前的1的个数为1,总逆序对数为4;
结束。

为了降低找第k大数的时间复杂度,我们先用快排对n个数进行降序排列,用树状数组进行求和统计。
为了记录第k大数的位置,我们用结构体来储存数据值和下标。

示例代码如下:

// 数据输入for (int i = 1; i <= n; ++i) {    scanf("%d", &num[i].val);    num[i].id = i;}// 降序排列sort(num+1, num+1+n, cmp);// 找到第k大数,统计它的下标之前总共1的个数,加到ans当中,最终输出ans即为结果// 标记第k大数的位置,并向上更新树状数组,两个操作顺序不能颠倒for (int i = 1; i <= n; ++i) {    ans += GetSum(num[i].id);    Update(num[i].id);}

3归并排序求解:

未完待续……

0 0