逆序数:

也是就说,对于n个不同的元素,先规定各元素之间有一个标准次序(例如n个 不同的自然数,可规定从小到大为标准次序),于是在这n个元素的任一排列中,当某两个元素的先后次序与标准次序不同时,就说有1个逆序。一个排列中所有逆序总数叫做这个排列的逆序数。

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

如数列 3 5 4 8 2 6 9

(5,4)是一个逆序对,同样还有(3,2),(5,2),(4,2)等等。

 什么是逆序数:

跟标准列相反序数的总和 

比如说 

标准列是1 2 3 4 5 

那么 5 4 3 2 1 的逆序数算法: 

5之前没有数,记为0.

看第二个,4之前有一个5,在标准列中5在4的后面,所以记1个 

类似的,第三个 3 之前有 4 5 都是在标准列中3的后面,所以记2个 

同样的,2 之前有3个,1之前有4个 

将这些数加起来就是逆序数=1+2+3+4=10 


再举一个 2 4 3 1 5 

4 之前有0个 

3 之前有1个 

1 之前有3个 

5 之前有0个 

所以逆序数就是1+3=4 

 

逆序数的求法:

1.一个一个的数

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

该方法的时间复杂度为O(n^2)

 

2.归并思想

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

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

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,

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

再如:

0 1 2 3 4

2 3 4 1 5

mid=2;

seq1:2 3 4 

seq2:1 5

2>1 count=mid-i+1=2-0+1=3;所以,逆序数为3.



#include<iostream>
#include<climits>
using namespace std;

int count=0;

void merge(int arr[],int first,int mid,int last)
{

int *tmpArr=new int[last-first+1];
int i=first,j=mid+1;
int cur=0;
while(i<=mid && j<=last)
{
if(arr[i]<arr[j])
{
tmpArr[cur++]=arr[i++];
}
else
{
tmpArr[cur++]=arr[j++];

count += mid-i+1;//只增加这一句便可求逆序数
}
}
if(i<=mid)
{
while(i<=mid)
tmpArr[cur++]=arr[i++];
}
else
{
while(j<=last)
tmpArr[cur++]=arr[j++];
}


for(int k=0;k<cur;k++)
{
arr[first++]=tmpArr[k];
}
delete[] tmpArr;
tmpArr=NULL;
}



void mergeSort(int arr[],int first,int last)
{
if(first==last)
return;
int mid=(first+last)/2;
mergeSort(arr,first,mid);
mergeSort(arr,mid+1,last);
merge(arr,first,mid,last);
}
int main()
{
//int a1[]={7,8,9,1,2,3,5};
//int a1[]={5,4,3,2,1}; //count=10
int a1[]={2,3,4,1,5}; //count=3

int len=sizeof(a1)/sizeof(a1[0]);
mergeSort(a1,0,len-1);
cout<<count<<endl;
}


整个代码只是在归并排序基础上加了一句话而已。

 

方法3:用树状数组

还是以刚才的序列

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,然后统计该节点下边及右边放置元素的个数

具体代码如下:



//用树状数组求逆序数
const int Length=8;
struct Node
{
int data;
int pos;
};
int LowBit(int num)
{
return num&(num^(num-1));
}
//自定义排序方法
int cmp(const void * num1,const void * num2)
{
return ((Node*)num2)->data-((Node *)num1)->data;
}
//统计pos节点下方及右方节点元素的个数
int sum(int *TreeArray,int pos)
{
int result=0;
while (pos)
{
result+=TreeArray[pos];
pos-=LowBit(pos);
}
return result;
}
//使pos位置的节点及其父节点的元素个数加1
void INC(int *TreeArray,int pos)
{
while(pos<Length)
{
TreeArray[pos]++;
pos+=LowBit(pos);
}
}
void insertNum(int *TreeArray,Node * seq,int &reverseNum)
{
for (int i=1;i<Length;i++)
{
reverseNum+=sum(TreeArray,seq[i].pos); //累加逆序数
INC(TreeArray,seq[i].pos); //将该节点及父节点的数加1
}
}
int main(int argc, char* argv[])
{
int array[]={3,5,4,8,2,6,9};
Node seq[Length];
int TreeArray[Length];
memset(TreeArray,0,sizeof(TreeArray));
for (int i=1;i<Length+1;i++)
{
seq[i].data=array[i-1];
seq[i].pos=i;
}
//从大到小排序
qsort(seq+1,Length-1,sizeof(Node),cmp);
int reverseNum=0;
//边插入边计数
insertNum(TreeArray,seq,reverseNum);
cout<<reverseNum<<endl;
return 0;
}