第二次接觸MergeSort與Inversions(逆序對),這次弄懂了每一個細節,很好的理解了兩者之間關係。下面總結一下相關技巧。
1.MergeSort和quickSort類似,主要採用了Divide-and-Conquer的思想,也就是將原本規模爲1的問題,二分爲規模相近的兩個獨立子問題,同時求解。因此這裏遞歸通常是常用技巧。這裏難點應該在邊界條件的判斷上。(當然這也是算法通有難點)。
既然提到了quickSort,這裏不得不對比一下兩種排序算法的區別。時間複雜度上,MergeSort經歷了一個先分後合的過程,而quickSort在劃分成子問題時就已經在排序了,因此分好即排序完畢。因此,雖然兩者時間複雜度理論上都小於O(nlogn),但是快排好很多,尤其是快排的軸點選取法採用“任意三點取中法”時。空間複雜度上,前者在每次合併時至少需要開闢(lo+hi)>>1大小的數組備份前半部分數據,空間複雜度較大(可以結合後面的代碼理解);而後者,只需在每次劃分爲子問題時,另開闢一個相關類型的空間存儲軸點pivot。綜上,可以看出,快排好很多。
2.Inversions。逆序對指序列中與理想次序不一致的點對數。其可以定量衡量InsertionSort的效率。比如升序序列中,如果秩i<j,但是i對應的值卻大於j,則i和j對應的值構成一對逆序對。降序,相反。注意,這裏採用前向統計方法。比如序列(5,6,2,2,3),元素3對應的逆序對爲2,分別爲(5,3),(6,3).這裏是關鍵
3.在MergeSort中如何統計Inversions?在相鄰序列歸併的過程過程中,如果一旦存在後序元素大於前序,則存在逆序對,且逆序對數恰好爲(lb-i)。其中,i爲前序元素的秩,lb爲前序元素的大小。請自行畫圖並結合一下代碼理解。
完整代碼如下:
#include <iostream>
#include <vector>
using namespace std;
vector<int*> sequence;
int cnt = 0; //統計逆序對數
void merge(int lo, int mid, int hi){
int lb = mid - lo;
int s = lo;
vector<int*> B(lb);
for (int i = 0; i < lb; i++){ //深拷貝
int* a = new int;
*a = *sequence[s++];
B[i] = a;
}
//開始比較操作
int i = 0;
//一共有四種邊界情況
while (hi>lo)
{
if ((i < lb) && (mid < hi)){
if (*B[i] <= *sequence[mid])
*sequence[lo++] = *B[i++];
else{
cnt += (lb - i); //統計逆序對
*sequence[lo++] = *sequence[mid++];
}
}
if ((i < lb) && !(mid < hi)){
*sequence[lo++] = *B[i++];
//cnt += (lb - i); //想一下這裏爲什麼要去掉
}
if (!(i<lb) && (mid < hi))
*sequence[lo++] = *sequence[mid++];
}
}
void mergeSort(int lo, int hi){
if (hi - lo < 2)
return; //相鄰元素自然有序
int mid = (lo + hi) >> 1;
mergeSort(lo, mid);
mergeSort(mid, hi);
merge(lo, mid, hi);
}
int main(){
int n;
scanf("%d", &n);
sequence.resize(n);
for (int i = 0; i < n; i++)
{
int* a = new int;
scanf("%d", a);
sequence[i] = a;
}
mergeSort(0, sequence.size());
for (int i = 0; i < n; i++)
{
cout << *sequence[i] << " ";
}
cout << endl << cnt << endl;
return 0;
}