1. 程式人生 > 其它 >歸併排序求逆序數的個數

歸併排序求逆序數的個數

技術標籤:歸併排序歸併法求逆序數筆記c語言

描述
在一個排列中,如果一對數的前後位置與大小順序相反,即前面的數大於後面的數,那麼它們就稱為一個逆序。一個排列中逆序的總數就稱為這個排列的逆序數。

現在,給你一個N個元素的序列,請你判斷出它的逆序數是多少。

比如 1 3 2 的逆序數就是1。

格式
輸入格式
第一行輸入一個整數T表示測試資料的組數(1<=T<=5)
每組測試資料的每一行是一個整數N表示數列中共有N個元素(2〈=N〈=100000)
隨後的一行共有N個整數Ai(0<=Ai<1000000000),表示數列中的所有元素。

資料保證在多組測試資料中,多於10萬個數的測試資料最多隻有一組。

在這裡我們要求逆序數如:1 3 5 2 4
【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;
可能有些小夥伴會有些疑問為什麼不是從i到j(i,j)的範圍全是逆序數呢?
因為啊,當前資料段都經過不斷分割的子序列排成了有順序的序列了,從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); //輸出逆序數的個數 } }