排序演算法之歸併排序及利用歸併排序求逆序數
阿新 • • 發佈:2019-02-19
排序演算法之歸併排序
1. 自頂向下的歸併排序
中心思想
將待排序的陣列平均切分為兩半,將前半部分和後半部分分別進行排序,再講兩個有序陣列歸併到一個數組中
特點
遞迴,需要額外的空間(輔助陣列)來儲存切分完成的子陣列,主要難點在於合併
操作步驟
- 將待排序陣列均分為兩半
- 對前半部分進行排序
- 對後半部分進行排序
- 合併兩個子陣列
- 遞迴呼叫以上過程
程式碼實現
public void sort(int[] a){
int N = a.length;;
int b = new int[N];//建立輔助陣列
sort(a, 0, N-1);
}
public void sort(int[] a, int low, int high){
if (low >= high) {
return;
}
int mid = (low + high) / 2;//均分陣列
sort(a, low, mid);//排前半部分
sort(a, mid, high);//排後半部分
merge(a, low, high, mid);//歸併
}
private void merge(int[] a, int low, int high, int mid){
for(int i = low; i <= high; i++) {
b[i] = a[i];
}//將a[low...high]複製到b[low...high]
int j = low;//掃描左半部分
int k = mid + 1;//掃描右半部分
for(int i = low; i <= high; i++){
// 從b[low...high]中依次取出較小的元素放入a[low...high]
if (j > mid) {
a[i] = b[k++];//若左半部分元素用盡,取右半部分元素
} else if (k > high) {
a[i] = b[j++]//若右半部分元素用盡,取左半部分元素
} else if (b[j] < b[k]) {
a[i] = b[j++];//兩半部分元素都未用盡時,取較小的
} else {
a[i] = b[k++];
}
}
}
2. 自底向上的歸併排序
特點
不切分陣列,直接兩兩歸併(將每個元素當做大小為1的子陣列)、四四歸併(將兩個大小為2的子陣列歸併為一個大小為4的陣列)…最後對整個陣列歸併
優點
相對於自頂向下的歸併,程式碼量少
程式碼實現
public void sort(int[] a){
int N = a.length;
int b[] = new int[N];
sort(a, 0, N-1);
}
private void sort(int[] a, int low, int high){
//sz為子陣列的大小,依次為1,2,4,8...N
for(int sz = 1; sz < N; sz = sz + sz){
//i為子陣列的索引,而子陣列大小為N,因而當i移動到靠近邊界時邊界元素的索引要滿足i+sz<N
for(int i = low; i < N - sz; i = i + sz + sz){
//當陣列大小不是2的偶數倍時,最後一個子陣列會比前一個要小,導致該子陣列的索引不滿足i+2sz-1,而是N-1
merge(a, i, min(i+2sz-1, N-1), i+sz-1);
//當N為2的偶數倍時,mid=(i+(i+2sz-1)-1)/2;非偶數倍時,mid=(i+N-1-1)/2,而i<N-sz,可推出此種情況下,(i+(i+2sz-1)-1)<(i+N-1-1),即N為偶數倍,mid可取mid=(i+(i+2sz-1)-1)/2
}
}
}
歸併排序的應用-求逆序數
逆序數指的是逆序數對的個數,在一個序列a[0, 1, 2, …N]中,對任意的i和j,如果i < j且a[i] > a[j],則a[i]和a[j]為逆序數對,一個佇列中所有的逆序數對數量之和即為該序列的逆序數。
蠻力法求逆序數
for(int i = 1; i < N; i++){
for(int j = i - 1; j >= 0; j-- ){
if(a[i] < a[j]){
count++;
}
}
}
顯然兩個迴圈的存在導致此法複雜度為n的平方。
利用歸併排序
merge過程中,由於兩個子陣列(假設為A和B)已各自有序,用i掃描A,用j掃描B,某次迴圈中,若a[i] > a[j],則該次迴圈中得到的逆序數count = A.length - i。對count進行累加後即可得到總逆序數。
由於歸併排序時間複雜度為NlogN(數學問題,本文不做證明),因而優於蠻力法。
實現方式
只需在merge方法中計算逆序對數量即可
private void merge(int[] a, int low, int high, int mid){
for(int i = low; i <= high; i++) {
b[i] = a[i];
}//將a[low...high]複製到b[low...high]
int j = low;//掃描左半部分
int k = mid + 1;//掃描右半部分
for(int i = low; i <= high; i++){
// 從b[low...high]中依次取出較小的元素放入a[low...high]
if (j > mid) {
a[i] = b[k++];//若左半部分元素用盡,取右半部分元素
} else if (k > high) {
a[i] = b[j++]//若右半部分元素用盡,取左半部分元素
} else if (b[j] < b[k]) {
a[i] = b[j++];//兩半部分元素都未用盡時,取較小的
} else {
a[i] = b[k++];
count = count + (mid - low + 1) - j;//累加該次迴圈的逆序對數量
}
}
}