歸併排序及其擴展問題

歸併排序

歸併排序是建立在歸併操作上的一種有效的排序算法,是採用分治法(Divide and Conquer)的一個非常典型的應用。
後面的擴展題目,小和問題和逆序對個數問題,用文字真是解釋的稀爛,自己都看不下去。。。還是再紙上畫一畫,Debug看一下代碼執行的流程會比較好。

算法代碼如下:

public class MergeSort {
    public static void main(String[] args) {
        int[] array = new int[]{4, 2, 9, 1, 0, 8, 5, 3};
        mergeSort(array, 0, array.length-1);
        System.out.println(Arrays.toString(array));
        return;
    }
    private static void mergeSort(int[] array, int left, int right) {
        if(left == right)
            return;
        int mid = left + ((right - left) >> 1);
        mergeSort(array, left, mid);
        mergeSort(array, mid+1, right);
        merge(array, left, mid, right);
    }
    private static void merge(int[] array, int left, int mid, int right) {
        int[] helper = new int[right - left + 1];
        int l1 = left, l2 = mid + 1;
        int helperIndex  = 0;

        while(l1 <= mid && l2 <= right){
            helper[helperIndex++] = (array[l1] < array[l2] ? array[l1++] : array[l2++]);
        }

        while(l1 <= mid){
            helper[helperIndex++] = array[l1++];
        }
        while(l2 <= right){
            helper[helperIndex++] = array[l2++];
        }

        for(int i = 0; i < helper.length; i++){
            array[left+i] = helper[i];
        }
    }
}

mergeSort(int[] array, int left, int right)方法中主要關注三個部分:

mergeSort(array, left, mid); //左半部分歸併排序
mergeSort(array, mid+1, right); //右半部分歸併排序
merge(array, left, mid, right); //左右兩邊進行合併

這也就是歸併排序主要的思路。

歸併排序的時間複雜度

這裏寫圖片描述
在master公式中,a是子問題的個數,N/b是子問題的規模,O(N^d)是其他的開銷時間。
在歸併排序中,子問題的個數是2,子問題的規模是N/2,至於其他的開銷,在歸併排序中就是左右兩邊進行合併的開銷,時間複雜度爲O(N)。所以,T(N) = 2*T(N/2) + O(N),也就是時間複雜度爲O(N * logN)。

擴展1——小和問題

[3, 1, 4, 5, 2]
3左邊比3小的數:
1左邊比1小的數:
4左邊比4小的數:3, 1
5左邊比5小的數:3, 1, 4
2左邊比2小的數:1
把這些數求和得到的就是小和: 13
public class MergeSortToSmallSum {
    public static void main(String[] args) {

        int[] array = new int[]{3, 1, 4, 5, 2};
        System.out.println(mergeSort(array, 0, array.length-1));
        return;
    }
    private static int mergeSort(int[] array, int left, int right) {
        if(left == right)
            return 0;
        int mid = left + ((right - left) >> 1);
        return mergeSort(array, left, mid)
                + mergeSort(array, mid+1, right)
                + merge(array, left, mid, right);
    }

    private static int merge(int[] array, int left, int mid, int right) {
        int[] helper = new int[right - left + 1];
        int l1 = left, l2 = mid + 1;
        int helperIndex  = 0;
        int result = 0;

        while(l1 <= mid && l2 <= right){
            result += array[l1] < array[l2] ? (right-l2+1) * array[l1] : 0;
            helper[helperIndex++] = (array[l1] < array[l2] ? array[l1++] : array[l2++]);
        }
        while(l1 <= mid){
            helper[helperIndex++] = array[l1++];
        }
        while(l2 <= right){
            helper[helperIndex++] = array[l2++];
        }
        for(int i = 0; i < helper.length; i++){
            array[left+i] = helper[i];
        }
        return result;
    }
}

擴展2——逆序對個數

[4, 3, 5, 2, 1]
4右邊比4小的數:3, 2, 1
3右邊比3小的數:2, 1
5右邊比5小的數:2, 1
2右邊比2小的數:1
1右邊比1小的數:
總共8對逆序對

小和問題中,我們還是按照從小到大對數組進行排序,因爲小和問題是要找到一個數左邊比自己小的數,所以在merge方法中,我們判斷左邊數比右邊數小的時候,那麼這個數比右邊數及其之後的數都要小,所以可以使用公式:

result += array[l1] < array[l2] ? (right-l2+1) * array[l1] : 0;

但是在逆序對問題中,是找左邊比右邊數大的,顯然使用從大到小排序更適合處理這個問題。判斷出左邊數比右邊數大的時候,那麼可以肯定左邊這個數,比右邊數及其後邊的數都要大。

public class MergeSortToInversion {

    public static void main(String[] args) {

        int[] array = new int[]{3, 4, 5, 2, 1};
        System.out.println(mergeSort(array, 0, array.length-1));
        System.out.println(Arrays.toString(array));
        return;
    }

    private static int mergeSort(int[] array, int left, int right) {
        if(left == right)
            return 0;
        int mid = left + ((right - left) >> 1);
        return mergeSort(array, left, mid)
                + mergeSort(array, mid+1, right)
                + merge(array, left, mid, right);
    }
    private static int merge(int[] array, int left, int mid, int right) {
        int[] helper = new int[right - left + 1];
        int l1 = left, l2 = mid + 1;
        int helperIndex  = 0;
        int result = 0;

        while(l1 <= mid && l2 <= right){
            result += array[l1] > array[l2] ? (right-l2+1) : 0;
            helper[helperIndex++] = (array[l1] < array[l2] ? array[l2++] : array[l1++]);
        }
        while(l1 <= mid){
            helper[helperIndex++] = array[l1++];
        }
        while(l2 <= right){
            helper[helperIndex++] = array[l2++];
        }
        for(int i = 0; i < helper.length; i++){
            array[left+i] = helper[i];
        }
        return result;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章