題目:在數組中的兩個數字如果前面一個數字大於后面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個數組中的逆序對的總數。例如,有一個數組為Array[0..n] 其中有元素a[i],a[j].如果 當i<j時,a[i]>a[j],那么我們就稱(a[i],a[j])為一個逆序對。在數組{7,5,6,4}中一共存在5對逆序對,分別是(7,6),(7,5),(7,4),(6,4),(5,4)。
參考文獻
排序算法匯總->歸並排序
解題思路
看到這樣的題目,最簡單的想法就是遍歷每一個元素,讓其與后面的元素對比,如果大於則count++,但是這樣的時間復雜度是o(n2)。這題有更好的解決方法,時間復雜度只需要o(nlogn)。其實這道題目的思路跟歸並排序差不多,求逆序對的過程就是一個求歸並排序的過程,在求出逆序對以后,原數組變得有序,是通過歸並排序得到的。
(1)總體的意思就是將數組分成兩段,首先求段內的逆序對數量,比如下面兩段代碼就是求左右兩端數組段內的逆序對數量
inversions+=InversePairsCore(arry,start,mid,temp);//找左半段的逆序對數目 inversions+=InversePairsCore(arry,mid+1,end,temp);//找右半段的逆序對數目
(2)然后求段間的逆序對數量,如下面的代碼
inversions+=MergeArray(arry,start,mid,end,temp);//在找完左右半段逆序對以后兩段數組有序,然后找兩段之間的逆序對。最小的逆序段只有一個元素。
(3)然后在求段間逆序對的時候,我們分為arry[start...mid]和arry[mid+1...end],然后設置兩個指針ij分別指向兩段數組的末尾元素,也就是i=mid,j=end。然后比較arry[i]和arry[j],
- 如果arry[i]>arry[j],因為兩段數組都是有序的,所以arry[i]>arry[mid+1...j],這些都是逆序對,我們統計出的逆序對為j-(mid+1)+1=j-mid。並且將大數arry[i]放入臨時數組temp[]當中,i往前移動
- 如果arry[i]<arry[j],則將大數arry[j]放入temp[]中,j往前移。
完整實現代碼

#include<iostream> #include<stdlib.h> using namespace std; void printArray(int arry[],int len) { for(int i=0;i<len;i++) cout<<arry[i]<<" "; cout<<endl; } int MergeArray(int arry[],int start,int mid,int end,int temp[])//數組的歸並操作 { //int leftLen=mid-start+1;//arry[start...mid]左半段長度 //int rightLlen=end-mid;//arry[mid+1...end]右半段長度 int i=mid; int j=end; int k=0;//臨時數組末尾坐標 int count=0; //設定兩個指針ij分別指向兩段有序數組的頭元素,將小的那一個放入到臨時數組中去。 while(i>=start&&j>mid) { if(arry[i]>arry[j]) { temp[k++]=arry[i--];//從臨時數組的最后一個位置開始排序 count+=j-mid;//因為arry[mid+1...j...end]是有序的,如果arry[i]>arry[j],那么也大於arry[j]之前的元素,從a[mid+1...j]一共有j-(mid+1)+1=j-mid } else { temp[k++]=arry[j--]; } } cout<<"調用MergeArray時的count:"<<count<<endl; while(i>=start)//表示前半段數組中還有元素未放入臨時數組 { temp[k++]=arry[i--]; } while(j>mid) { temp[k++]=arry[j--]; } //將臨時數組中的元素寫回到原數組當中去。 for(i=0;i<k;i++) arry[end-i]=temp[i]; printArray(arry,8);//輸出進過一次歸並以后的數組,用於理解整體過程 return count; } int InversePairsCore(int arry[],int start,int end,int temp[]) { int inversions = 0; if(start<end) { int mid=(start+end)/2; inversions+=InversePairsCore(arry,start,mid,temp);//找左半段的逆序對數目 inversions+=InversePairsCore(arry,mid+1,end,temp);//找右半段的逆序對數目 inversions+=MergeArray(arry,start,mid,end,temp);//在找完左右半段逆序對以后兩段數組有序,然后找兩段之間的逆序對。最小的逆序段只有一個元素。 } return inversions; } int InversePairs(int arry[],int len) { int *temp=new int[len]; int count=InversePairsCore(arry,0,len-1,temp); delete[] temp; return count; } void main() { //int arry[]={7,5,6,4}; int arry[]={1,3,7,8,2,4,6,5}; int len=sizeof(arry)/sizeof(int); //printArray(arry,len); int count=InversePairs(arry,len); //printArray(arry,len); //cout<<count<<endl; system("pause"); }
輸出結果:
調用MergeArray時的count:0 1 3 7 8 2 4 6 5 調用MergeArray時的count:0 1 3 7 8 2 4 6 5 調用MergeArray時的count:0 1 3 7 8 2 4 6 5 調用MergeArray時的count:0 1 3 7 8 2 4 6 5 調用MergeArray時的count:1//這是因為上面65之間有段內的逆序對 1 3 7 8 2 4 5 6 調用MergeArray時的count:0 1 3 7 8 2 4 5 6 調用MergeArray時的count:9//這里全部都是段間的逆序對,(3,2),(7,2),(7,4),(7,5),(7,6),(8,2),(8,4),(8,5),(8,6),一共有九個 1 2 3 4 5 6 7 8 逆序對數量:10