歸併排序求逆序數的個數
阿新 • • 發佈:2020-12-26
描述
在一個排列中,如果一對數的前後位置與大小順序相反,即前面的數大於後面的數,那麼它們就稱為一個逆序。一個排列中逆序的總數就稱為這個排列的逆序數。
現在,給你一個N個元素的序列,請你判斷出它的逆序數是多少。
比如 1 3 2 的逆序數就是1。
格式
輸入格式
第一行輸入一個整數T表示測試資料的組數(1<=T<=5)
每組測試資料的每一行是一個整數N表示數列中共有N個元素(2〈=N〈=100000)
隨後的一行共有N個整數Ai(0<=Ai<1000000000),表示數列中的所有元素。
資料保證在多組測試資料中,多於10萬個數的測試資料最多隻有一組。
【3 5 2】2是3的逆序數,2也是5的逆序數
【5 2 4】4是5的逆序數。
因此這組數的逆序數的個數為3.
在這裡,我們選擇用歸併排序解決。
其實歸併就是分治思想的一種,將一組資料分割成一個個子序列進行排序處理,從而來完成整體的排序處理。
【1 ,3 ,5 ,2, 4】
【1, 3,5】|【2, 4】
【1,3】【5】|【2】【4】
【1】【3】 【5】 【2】 【4】
這樣便拆分成最小段了,然後在進行排序組合
【1 3】【5】【2 4】
【1 3 5】【2 4】
【1 2 3 4 5】
需要注意的是求逆序數時,如果後段的資料a[j] 比前面段的資料a[i]小的話那逆序數的個數就是從(i,mid)這些數全部都為逆序數,所以累加器就要加上mid-i+1;
因為啊,當前資料段都經過不斷分割的子序列排成了有順序的序列了,從mid到j-1的資料都是升序(有順序的)所以對於j來說前面(mid,j-1)的資料對於a[j]來說不構成逆序。
就如【1, 3,5】和【2,4】兩段有序的資料2為3,5的逆序
4是5的逆序,就只有5一個數,而它前面的數2對於它來說不是逆序。
只需要Mid-i+1,而不是j-i;
下面看原始碼:
#include<stdio.h>
long long s;
int sort(int *a,int *b,int left,int mid,int right);
void fun(int *a,int *b,int left,int right)
{
int i,j,mid;
if(left==right)
return ;
mid=(left+right)/2; //將陣列從中間切割
fun(a,b,left,mid); //將前半部分資料進行處理,包括mid
fun(a,b,mid+1,right);//將後半部分資料處理
sort(a,b,left,mid,right); //分割完成就對當前段的資料進行歸併排序
}
int sort(int *a,int *b,int left,int mid,int right)
{
int i,j,k=left;
i=left,j=mid+1;
while(i<=mid&&j<=right) //歸併排序,哪個小就將資料插入到b陣列
{
if(a[i]<=a[j])
b[k++]=a[i++];
else
{
b[k++]=a[j++];//這裡說明資料是無序的,就可以累積逆序資料的個數(對於後段的a[j]來說從i到mid的數都是逆序數)
s+=mid-i+1; //累加從i到mid的逆序數的個數
}
}
while(i<=mid) //如果前段資料還沒有資料沒有插入進b就迴圈插入
b[k++]=a[i++];
while(j<=right) //將後段未插入的資料插入進b陣列
b[k++]=a[j++];
for(i=left; i<=right; i++) //因為a陣列中的資料全部有序保留在b陣列,現在將有序的資料copy到a陣列,範圍是同樣是(left,right)
a[i]=b[i];
return 0;
}
int main()
{
int a[1000000];
int b[1000000];
//b陣列是用來將原陣列a排序的資料暫存,下標與原陣列a下標一一對應,可以理解為空間重疊
int i,j,n,t;
scanf("%d",&t);
while(t--)
{
s=0;
scanf("%d",&n);
for(i=0; i<n; i++)
scanf("%lld",&a[i]);
fun(a,b,0,n-1);
printf("%lld\n",s); //輸出逆序數的個數
}
}