1. 程式人生 > >排序演算法之歸併排序及利用歸併排序求逆序數

排序演算法之歸併排序及利用歸併排序求逆序數

排序演算法之歸併排序

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;//累加該次迴圈的逆序對數量
        }
    }

}