歸並排序
所謂歸並排序是指將兩個或兩個以上有序的數列(或有序表),合並成一個仍然有序的數列(或有序表)。這樣的排序方法經常用於多個有序的數據文件歸並成一個有序的數據文件。歸並排序的算法比較簡單。
1. 基本思想
歸並排序是用分治思想,分治模式在每一層遞歸上有三個步驟:
- 分解(Divide):將n個元素分成2個含n/2個元素的子序列(n為奇數時,有一個序列會多一)。
- 解決(Conquer):用合並排序法對兩個子序列遞歸的排序。
- 合並(Combine):合並兩個已排序的子序列已得到排序結果。
2. 實現邏輯
2.1 工作原理
① 申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合並后的序列
② 設定兩個指針,最初位置分別為兩個已經排序序列的起始位置
③ 比較兩個指針所指向的元素,選擇相對小的元素放入到合並空間,並移動指針到下一位置
④ 重復步驟③直到某一指針到達序列尾
⑤ 將另一序列剩下的所有元素直接復制到合並序列尾
2.2 遞歸實現
① 將序列每相鄰兩個數字進行歸並操作,形成floor(n/2)個序列,排序后每個序列包含兩個元素
② 將上述序列再次歸並,形成floor(n/4)個序列,每個序列包含四個元素
③ 重復步驟②,直到所有元素排序完畢
4. 復雜度分析
平均時間復雜度:O(nlogn)
最佳時間復雜度:O(n)
最差時間復雜度:O(nlogn)
空間復雜度:O(n)
排序方式:In-place
穩定性:穩定

#include<cstdio> const int maxn=500010; int a[maxn],c[maxn]; int n; long long ans=0; void msort(int l,int r){ if(l==r) return ; int mid=(l+r)/2; msort(l,mid);//分解 msort(mid+1,r); int i=l,j=mid+1,k=l; while(i<=mid&&j<=r){//合並 if(a[i]>a[j]) { // ans+=mid-i+1; c[k++]=a[j++]; } else c[k++]=a[i++]; } while(i<=mid) c[k++]=a[i++]; while(j<=r) c[k++]=a[j++]; for(int i=l;i<=r;i++) a[i]=c[i]; } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); msort(1,n); for(int i=1;i<=n;i++) printf("%d",a[i]); //printf("%lld\n",ans);//逆序對數 return 0; }
歸並的空間復雜度就是那個臨時的數組和遞歸時壓入棧的數據占用的空間:n + logn;所以空間復雜度為: O(n)。
歸並排序算法中,歸並最后到底都是相鄰元素之間的比較交換,並不會發生相同元素的相對位置發生變化,故是穩定性算法。
歸並排序應用
問題1:求通過相鄰位置的數字交換,使數列有序的最少交換次數?
問題2:求數列中的逆序對數。
這兩個問題實際上是一個問題。
歸並排序的交換次數就是這個數列的逆序對個數(也是使數列有序的相鄰兩元素交換的最少交換次數),為什么呢?
歸並排序是將數列a[l,h]分成兩半a[l,mid]和a[mid+1,h]分別進行歸並排序,然后再將這兩半合並起來。
在合並的過程中(設l<=i<=mid,mid+1<=j<=h),當a[i]<=a[j]時,並不產生逆序數;
當a[i]>a[j]時,在前半部分中比a[i]大的數都比a[j]大,將a[j]放在a[i]前面的話,通過相鄰兩數兩兩交換,交換次數為mid+1-I,即逆序數要加上mid+1-i。
因此,可以在歸並排序中的合並過程中計算逆序數,也即求出了相鄰兩數交換的最少次數。
比如
i=1 mid=2 j=3
3 8 | 2 6
右邊的2如果想放到3的前面,需要分別和8交換,再和3 交換,交換次數為2,即逆序對數為2=mid-i+1
A數列:3 2 1 4 B數列:2 3 1 4
下標: 1 2 3 4 下標: 1 2 3 4
A排序后:1 2 3 4 B排序后: 1 2 3 4
對應下標:3 2 1 4 對應下標:3 1 2 4
排序后如果A和B對應的下標相等,則結束,如果不相等則需要交換。
C[Ai下標]=Bi下標,
即如果c[i]=i,則什么也不用做,否則對c排序,使c[i]=i,求逆序對數即可
題目應用:洛谷:P1309,P1908,P1966