1. 程式人生 > >陣列中的逆序對(分治、遞迴與合併)

陣列中的逆序對(分治、遞迴與合併)

在陣列中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。
輸入一個數組,求出這個陣列中的逆序對的總數P。
將P對1000000007取模的結果輸出。 即輸出P%1000000007
輸入描述:
題目保證輸入的陣列中沒有的相同的數字

資料範圍:

	對於%50的資料,size<=10^4

	對於%75的資料,size<=10^5

	對於%100的資料,size<=2*10^5


示例:
輸入
1,2,3,4,5,6,7,0
輸出
7

思路:暴力解法是雙層迴圈逐一判斷,時間複雜度為O(n^2). 本題的更優解法是分治與合併。該題的思路過程可以參考劍指OFFER上的解釋,不再贅述,下面只寫出我的思路。首先,

有序陣列是一種最原始的資料結構,但在很多演算法上因其有序性而比無序陣列快很多。陣列從無序到有序的排序演算法的時間複雜度普遍是O(nlogn),所以若一個無序陣列的某種演算法超過這個複雜度後,可以考慮先將其進行排序。其次,分治法由兩部分組成:劃分成子問題遞迴、將子問題合併。分治法時間複雜度的關鍵在於合併的複雜度。事實上所有問題都能分治,但只有合併演算法夠簡單時,分治法才有意義(關於這一點可以參考我的這篇博文)。最後討論本題,可以先把原陣列分成兩個子陣列,統計兩個子陣列內部的逆序對,再統計兩個陣列之間的逆序對,兩者之和即為總的逆序對數。可問題來了,我們來求兩個子陣列之間的逆序對,從前面的子陣列中依次取出一個數(n/2個數),與後面的子陣列中的每個數進行比較(n/2個數),所以合併的時間複雜度是O(n^2),根據博文可以計算出總的時間複雜度,發現沒有任何優化!根據上面三點,一個很自然的想法出現了:在統計逆序對的同時進行排序
即計算逆序數的函式同時也要負起排序的任務。當兩個子陣列均是有序的時候,子陣列之間的逆序數的計算會非常簡單:用兩個下標從兩個陣列的尾部(值較大的一端)開始向左滑動,假設這兩個序號分別是 i 和 j ,分別對應兩個數值 ai 和 bj (a陣列在b陣列之前),有:若 ai>bj, 則 ai 大於 bj 及 bj 左邊的所有數; 若 ai<bj, 則 ai 及 ai 左邊的所有數都小於 bj;

對陣列的排序過程用分治法,其中劃分的部分和統計逆序對數是一致的,所以共同完成;而合併的部分就是將兩個有序的子數組合併成大的有序陣列,這需要額外的儲存空間。比如陣列nums[0,9] 劃分成a[0,4]和b[5,9],合併a和b時需要有一個長度為10的輔助空間copy[0,9],存放排好序的元素。當遞迴到上一層時,不需要把copy的值複製給nums,而是調換兩者的位置(這一點非常重要)。上一層的copy就是下一層的nums, 上一層的nums就是下一層的copy。

從而我們需要合併的陣列總是當前排序程度更優的那一個,而當前排序程度更差的那個陣列作為輔助空間,來儲存當前合併排序過後的元素。

程式碼:

//函式功能:把data陣列的[start,end]區間排序後儲存在copy陣列的[start,end]區間內;
//			同時統計該區間內的逆序對數。
//			在區間[start,end]內data和copy的元素相同(雖然順序不同)。
long long InversePairsCore(vector<int> &data, vector<int> &copy, int start, int end){
	if (start == end){
		copy[start] = data[start];
		return 0;
	}
	int length = (end - start) / 2;
	//區間[start,end]一分為二:[start,mid]和[mid+1,end];
	//把copy陣列的左部分排序並存入data左部分,統計左部分的逆序數left
	long long left = InversePairsCore(copy, data, start, start + length);
	//把copy陣列的右部分排序並存入data右部分,統計右部分的逆序數right
	long long right = InversePairsCore(copy, data, start + length + 1, end);
	//把兩部分有序的data合併然後存入copy,統計左部分與右部分之間的逆序數count
	int i = start + length;
	int j = end;
	int indexcopy = end;
	long long count = 0;
	while (i >= start&&j >= start + length + 1){
		if (data[i]>data[j]){
			copy[indexcopy--] = data[i--];
			count+=j - start - length;
		}
		else
			copy[indexcopy--] = data[j--];
	}
	//左右部分中有一個已經取完,則另一個依次加入copy即可
	for (; i >= start; i--)
		copy[indexcopy--] = data[i];
	for (; j >= start + length + 1; j--)
		copy[indexcopy--] = data[j];
	return left + right + count;
}