1. 程式人生 > >歸併排序(包含逆序數對的個數51Nod1019)

歸併排序(包含逆序數對的個數51Nod1019)

歸併排序是效率很好的排序方式,和快排效率一樣高,但在穩定性上優於快排,下面我們來介紹歸併排序。

歸併排序運用遞迴將序列不斷二分(其原理就是分治),就像一棵樹不斷向下分支,最後分到只剩一個元素,這樣這個元素就可當做有序的,因為只有一個元素嘛。然後是合併,怎麼分出來就怎麼合併回去,不過既然是排序,那麼合併的時候就需要比較一下大小了。

下面為了更好的理解,我們來看一張圖片。(這張圖是借用的,很感謝製圖人)

這就是一個簡單序列的歸併排序過程。

下面讓我們來看程式碼。

#include<cstdio>
const int Max=50001;
int temp[Max],num=0;
void mergearray(int a[],int first,int mid,int last){//將陣列按順序合併
	int i=first,j=mid+1,m=mid,n=last,k=0;
	while(i<=m&&j<=n){//這裡的等號很重要,沒有的話前半段的資料不能全部遍歷
		if(a[i]<=a[j]) temp[k++]=a[i++];
		else{
			temp[k++]=a[j++];
			num+=mid-i+1; //統計逆序數對 
		}
	}
	while(i<=m) temp[k++]=a[i++];    //這裡等號很重要,不然會漏資料
	while(j<=n) temp[k++]=a[j++];//這裡等號很重要,不然會漏資料
	for(int q=0;q<=last-first;q++)
		a[first+q]=temp[q];
}

void mergesort(int a[],int first,int last){//將陣列二分處理
	if(first<last){
		int mid=(first+last)/2;  
		mergesort(a,first,mid);  //左邊有序
		mergesort(a,mid+1,last);//右邊有序
		mergearray(a,first,mid,last);
	}
}
int main()
{
	int a[Max],n;
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%d",&a[i]);
	}
	mergesort(a,0,n-1);
	for(int i=0;i<n;i++)
		printf("%d\n",a[i]);//統計逆序數的話輸出num即可。
}

這裡可能有的人不太明白逆序數為什麼num+=mid-i+1這樣算,這裡做一下說明,在數組合並時計算前面的數是否比後面的大,這裡要注意合併時前後兩部分已經是有序,如果此時啊a[i]>a[j],說明a[first]到a[i-1]全部小於a[j],而a[mid+1]到a[j-1]全部小於a[j],那麼意思就是大於a[j]的數全部在a[i]到a[mid]之間,a[i]到a[mid]共有mid-i+1個數,所以逆序數對num此時要加上mid-i+1。

本人實力有限,如有錯誤,歡迎指出,謝謝。