進階指南--超快速排序(歸併+逆序對)

水牛的夢想 發表於 2020-10-17

歸併排序:

  將陣列切成兩半,然後左邊排完 右邊排完 (二分遞迴)  合併排列(用臨時陣列:對左右兩邊的數誰大誰進去)

逆序對

對於i,j (i < j ) 如果 a[i] < a[j] 則構成一個逆序對

那思考:歸併排序 你知道左邊一個單調遞增數列,右邊一個單調遞增數列,mid是左右分界,屬於左邊,那對於右邊的數 a[j] 而言,在合併的時候,如果發現自己小於前面的一個數 a[i] 進了陣列, 那說明 a[i]~a[mid] 都比a[j] 大,構成了(mid-i+1) 個逆序對
這樣子,在合併的時候記錄逆序對,通過歸併排序就記錄了逆序對的個數

超快速排序

相鄰交換,你會發現如果交換一次 a[i],a[i+1],(a[i] > a[i+1])就等價於減少一個逆序對,因為要最小次數,那你每次選相鄰兩個 a[i] > a[i+1] 即為最優,那麼這樣子做的結果就是把逆序對變為0,結果數即為逆序對的總個數

#include <bits/stdc++.h>
using namespace std;
const int MA = 5e5+10;
int a[MA],b[MA];
long long cnt;//小心溢位!! 
void merge (int l,int r){
	if(l >= r) return ;
	//切成兩半 
	int mid = (l+r)>>1;
	merge(l,mid);
	merge(mid+1,r);
	//開始合併兩個有序佇列 
	int i = l,j = mid+1; 
	for (int k = l; k <= r; k++){
		//邊界和正常情況 
		if(j > r ||  (i <= mid && a[i]<a[j] ) ) b[k] = a[i++];//左小於右仍有序
		else   b[k] = a[j++], cnt += mid - i + 1; 
	} 
	for (int k = l; k <= r; k++) a[k] = b[k];
	return;
} 
int main()
{
	int n;
	while(scanf("%d",&n)==1 && n){
		for (int i = 0; i < n; i++) scanf("%d",&a[i]);
		cnt = 0;
		merge(0,n-1);
		cout<<cnt<<endl; 
	}
	return 0;
}