归并排序及其扩展问题

归并排序

归并排序是建立在归并操作上的一种有效的排序算法,是采用分治法(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;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章