逆序对问题---求逆序数

Posted kimsimple

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了逆序对问题---求逆序数相关的知识,希望对你有一定的参考价值。

逆序数:在一个排列中,如果一对数的前后位置与大小顺序相反,
    即前面的数大于后面的数,那么它们就称为一个逆序
    一个排列中逆序的总数就称为这个排列的逆序数
逆序数为偶数的排列称为偶排列;逆序数为奇数的排列称为奇排列。

{

设 A 为一个有 n 个数字的有序集 (n>1),其中所有数字各不相同。

如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 <A[i], A[j]> 这一个有序对称为 A 的一个逆序对,也称作逆序。逆序对的数量称作逆序数

例如:数组 <2,3,8,6,1> 的逆序对为:<2,1> <3,1> <8,1> <8,6> <6,1> 共5个逆序对。

对于<2,1>:1 ≤ 1 < 5 ≤ 5 ,A[1] > A[5],所以<A[1],A[5]>为一个合法的逆序对。

目前求逆序对数目比较普遍的方法是利用归并排序做到O(n\\log n)的时间复杂度。

当然,也可以利用树状数组、线段树来实现这种基础功能。复杂度均为O(n\\log n)

}

求逆序数方法:

感谢:http://blog.csdn.net/dlengong/article/details/7594919

法一:暴力  O(n^2)

最简单也是最容易想到的方法就是,对于数列中的每一个数a[i],遍历数列中的数a[j](其中j<i),若a[i]<a[j],则逆序数加1,

这样就能统计出该数列的逆序数总和

 

法二:归并的思想,利用归并排序过程

归并排序的复杂度为O(nlogn),当然此方法的复杂度也为O(nlogn)

有一种排序的方法是归并排序,归并排序的主要思想是将整个序列分成两部分,分别递归将这两部分排好序之后,再和并为一个有序的序列,核心代码如下

MergeSort(first,last)

{

If(first==last)

return

Int med=(first+last)/2;

MergeSort(first,med);

MergeSort(med+1,last);

Merge(first,last);

}

  

 

在合并的过程中是将两个相邻并且有序的序列合并成一个有序序列,如以下两个有序序列

Seq1:3  4  5

Seq2:2  6  8  9

合并成一个有序序:

Seq:2  3  4  5  6  8  9

对于序列seq1中的某个数a[i],序列seq2中的某个数a[j],如果a[i]<a[j],没有逆序数,如果a[i]>a[j],那么逆序数为seq1中a[i]后边元素的个数(包括a[i]),即len1-i+1,

这样累加每次递归过程的逆序数,在完成整个递归过程之后,最后的累加和就是逆序的总数

const int LENGTH=100;
int temp[LENGTH];  //额外的辅助数组

int count=0;

void Merge(int * array,int first,int med,int last)
{
    int i=first,j=med+1;
    int cur=0;
    while (i<=med&&j<=last)
    {
        if (array[i]<array[j])
        {
            temp[cur++]=array[i++];
        }
        else
        {
            temp[cur++]=array[j++];
            <span style="color:#ff0000;">count+=med-i+1</span>;  //核心代码,逆序数增加
        }
    }
    while (i<=med)
    {
        temp[cur++]=array[i++];
    }
    while (j<=last)
    {
        temp[cur++]=array[j++];
    }
    for (int m=0;m<cur;m++)
    {
       array[first+m]=temp[m]; 
    }
}
void MergeSort(int *array,int first,int last)
{
    if (first==last)
    {
        return ;
    }
    int med=first+(last-first)/2;
    MergeSort(array,first,med);
    MergeSort(array,med+1,last);
    Merge(array,first,med,last);
}

法三:树状数组

还是以刚才的序列

3  5  4  8  2  6  9

大体思路为:新建一个数组,将数组中每个元素置0

0  0  0  0  0  0  0

取数列中最大的元素,将该元素所在位置置1

0  0  0  0  0  0  1

统计该位置前放置元素的个数,为0

接着放第二大元素8,将第四个位置置1

0  0  0  1  0  0  1

统计该位置前放置元素的个数,为0

继续放第三大元素6,将第六个位置置1

0  0  0  1  0  1  1

统计该位置前放置元素的个数,为1

这样直到把最小元素放完,累加每次放元素是该元素前边已放元素的个数,这样就算出总的逆序数来了

在统计和计算每次放某个元素时,该元素前边已放元素的个数时如果一个一个地数,那么一趟复杂度为O(n),总共操作n趟,复杂度为O(n^2),和第一种方法的复杂度一样了,那我们为什么还用这么复杂的方法

当然,在每次统计的过程中用树状数组可以把每一趟计数个数的复杂度降为O(logn),这样整个复杂度就变为O(nlogn)

 

树状数组是一种很好的数据结构,这有一篇专门描述树状数组的文章

将序列中的每个数按照从大到小的顺序插入到树状数组中,给当前插入节点及其父节点的个数加1,然后统计该节点下边及右边放置元素的个数

 

 

HDU1394

求逆序数

法一:数学  逆序数性质

对于这个题求把第一个数放到最后一个数的最小逆序数,对于原序列而言,如果把第一个数放到最后一个数,逆序列增加n-num[0]+1,逆序列减少num[0].

如何得到的?

#include "iostream"
#include "cstdio"
using namespace std;
#include <stdio.h>
int main()
{
    int num[5005],n;
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=0;i<n;i++)
        scanf("%d",&num[i]);
        int sum=0,temp;
        for(int i=0;i<n;i++)
            for(int j=i+1;j<n;j++)
                if(num[i]>num[j]) sum++;
        temp=sum;
        for(int i=n-1;i>=0;i--)
        {
            temp-=n-1-num[i];
            temp+=num[i];
            if(temp<sum)
            sum=temp;
        }
        printf("%d\\n",sum);
    }
    return 0;
} 

 

法二:树状数组解法

分析:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
#define N 5005
#define inf 999999999

int c[N];
int a[N];

int lowbit(int x){
    return x&(-x);
}

void update(int x){
    while(x<N){
        c[x]++;
        x+=lowbit(x);
    }
}

int get_sum(int x){
    int ans=0;
    while(x>0){
        ans+=c[x];
        x-=lowbit(x);
    }
    return ans;
}

int main()
{
    int n, i, j, k;
    while(scanf("%d",&n)==1){
        memset(c,0,sizeof(c));
        int num=0;
        for(i=1;i<=n;i++){
            scanf("%d",&a[i]);
            a[i]++;
            num+=get_sum(N-1)-get_sum(a[i]);
            update(a[i]);
        }                                  ///以上用树状数组求序列a的逆序数
        int minh=inf;
        for(i=1;i<=n;i++)
        {
            num=num-(a[i]-1)+(n-a[i]);       ///每次由之前的逆序数推出此时的逆序数
            minh=min(minh,num);             ///更新最小值
        }
        printf("%d\\n",minh);
    }
}

 两代码区别之处就在于求逆序数的方法。

树状数组虽然更复杂,但其统计速度更快,选取那种,还得看情况。

可以看看差距

就这个题,3倍?

以上是关于逆序对问题---求逆序数的主要内容,如果未能解决你的问题,请参考以下文章

树状数组求逆序对

震惊!Vector两行代码求逆序对,六行代码过普通平衡树

归并排序求逆序对

1237:求排列的逆序数

[树状数组]求排列的逆序数

求排列的逆序数