归并排序【MergeSort】

归并排序

归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案”修补”在一起,即分而治之)。

首先我们可以思考:如何将两个有序的序列合并,并且合并后依然有序呢?
这个比较简单,我们只需要用两个指针,一开始都指向两个序列的头部,然后开始比较两个指针所指数的大小,将小的那个放入一个新建的空间中,然后将它从相应的队列中删除,同时相应队列的指针后移一位;如此往复这个操作,那么两个有序的序列就合并了。

public int[] merge(int arrA[], int arrB[]) {
    // 创建合并后的数组
    int[] result = new int[arrA.length + arrB.length];
    int p1 = 0; // arrA的指针
    int p2 = 0; // arrB的指针
    int i = 0;  // 当前要放入result的位置
    // 比较两个指针所指数的大小,将小的那个放入新的数组中,同时相应队列的指针后移
    while(p1 < arrA.length && p2 < arrB.length) {
        result[i++] = arrA[p1] < arrB[p2] ? arrA[p1++] : arrB[p2++];
    }

    // 现在必有一个序列已经过完了,下面的循环只会进入一个
    while(p1 < arrA.length) {
        // 当前arrB走完了,将arrA中剩下的直接放入结果数组中
        result[i++] = arrA[p1++];
    }

    while(p2 < arrB.length) {
        // 当前arrA走完了,将arrB中剩下的直接放入结果数组中
        result[i++] = arrB[p2++];
    }
    return result;
}

归并排序就用到了这样的思想:先把大的数组拆分成两个序列,将这两个序列分别进行排序,然后再合并起来。
可能有人会想,拆分成的两个序列又怎么排序呢?很简单,我们继续将它拆分,当其不能拆分了,就认为这个不能拆分的个体已经排好序了,然后就可以进行合并了。

假设初始的数组是[5,6,4,7,3,2],我们可以先将其分成两个小的部分进行排序,进而再合并起来。
1. 首先拆分成两个部分:[5,6,4][7,3,2],将这两个部分分别排序后,再用外排的方式将两者合并排序起来。但是这两个部分怎么分别排序呢?我们可以继续拆分
2. 将[5,6,4][7,3,2]两个部分再进行拆分,得到:[5,6],[4][7,3],[2];现在又可以继续将它们拆分
3. 继续拆分得到{[5],[6][4]} {[7][3],[2]}; 现在已经无法继续拆分了,就可以进行合并了
4. 第一次归并后:[5,6][4][3,7][2]
5. 第二次归并后:[4,5,6][2,3,7]
6. 第三次归并后:[2,3,4,5,6,7]

算法实现

public class MergeSort {

    public void mergeSort(int[] arr) {
        if(arr == null || arr.length < 2) {
            return ;
        }
        mergeSort(arr, 0, arr.length - 1);
    }

    /**
        * 将数组在指定范围内进行拆分合并
        */
    public void mergeSort(int[] arr, int l, int r) {
        // 已经无法再拆分了
        if(l == r) {
            return ;
        }
        // 从中间拆分
        int mid = l + ((r-l) >> 1);
        mergeSort(arr, l, mid);
        mergeSort(arr, mid + 1, r);
        // 进行合并
        merge(arr, l, mid, r);
    }

    /**
    * 合并,l到m,m+1到r 两段分别有序
    */
    public void merge(int[] arr, int l, int m, int r) {
        int[] help = new int[r - l + 1];
        int i = 0;
        int p1 = l;
        int p2 = m + 1;
        while (p1 <= m && p2 <= r) {
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        while (p1 <= m) {
            help[i++] = arr[p1++];
        }
        while (p2 <= r) {
            help[i++] = arr[p2++];
        }
        for (i = 0; i < help.length; i++) {
            arr[l + i] = help[i];
        }
    }
}

时间复杂度

这里到了递归的算法,每一个递归都可以转换成非递归的方式。
递归分治法有一个Master公式,可以用来求解时间复杂度
简单的提一下Master公式:
T(N) = a * T(N/b) + O(N^d), N表示样本量,N/b表示子过程的样本量,a表示子过程执行次数,O(N^d)表示除去子过程之外,剩下的时间复杂度

我们这里是将数组从中间进行拆分,分成两段分别排序,所以子过程样本量是N/2;左右两个子过程,所以执行两次,a为2;剩下了一个合并的排序,时间复杂度为O(N)
所以最终的Master公式是:T(n)=2T(n/2)+O(N),

得到公式后,就可以去套答案了:
1. log(b,a) > d: 时间复杂度O(N^log(b,a))
2. log(b,a) = d: 时间复杂度O(N^d * logN)
3. log(b,a) < d: 时间复杂度O(N^d)

所以最终的时间复杂度是:O(N*logN)

空间复杂度

O(N)

稳定性

归并排序是稳定的排序

猜你喜欢

转载自blog.csdn.net/u013435893/article/details/79968617