排序-归并排序(Merge Sort)

描述

归并排序是将一个大的无序序列,可以先将它分成两半分别排序(递归地),然后将结果归并起来,最终将整个无序序列排序成有序序列。归并排序能够将长度为N的序列排序所需时间和NlongN成正比,归并排序的缺点是需要花费额外和N成正比的空间。归并排序是一种渐进最优的基于比较排序的算法。

归并排序使用的就是分治思想,分治,顾名思义就是分而治之,将一个大问题分解成小的问题来解决。小的问题解决了,大问题也就解决了。

性质

稳定性:稳定

时间复杂度:O(NlogN)

空间复杂度: O(N)

归并排序实现
public class MergeSorted extends AbstractSorted {

    // 归并所需要的辅助空间
    private Comparable[] temp;

    @Override
    public <T extends Comparable<T>> void sort(T[] arrays) {
        // 分配空间
        temp = new Comparable[arrays.length];
        // 归并排序
        sort(arrays, 0, arrays.length - 1);
    }

    /**
     * 归并排序
     * @param arrays 原数据
     * @param low    低位下标(左下标)
     * @param height 高位下标(右下标)
     */
    private void sort(Comparable[] arrays, int low, int height) {

        // 表示已经不能将数组分解成更小的数组了
        if (height <= low) {
            return;
        }

        // 获取数组中间下标位置
        int mid = low + (height - low) / 2;

        // 分治递归,将数组分成左部分[low, mid]和右部分[mid+1, height]进行处理
        // 左半边排序
        sort(arrays, low, mid);
        // 右半边排序
        sort(arrays, mid + 1, height);

        // 归并结果(将左部分[low, mid]和右部分[mid+1, height]排序后的结果进行归并)
        // 注释: 此时左部分[low, mid]和右部分[mid+1, height]是已经排好顺序的。归并只是将小结果归并成大结果。
        merge(arrays, low, mid, height);
    }

    /**
     * 归并排序, 左部分[low, mid]和右部分[mid+1, height]是已经排好顺序的。归并只是将小结果归并成大结果。
     * <p>
     *
     *     假设arrays=[3, 8, 9, 11, 1, 5, 7], low = 0, mid = 3, height=6
     *     此时,temp = arrays = [3, 8, 9, 11, 1, 5, 7], left = low = 0, right = mid + 1 = 4
     *
     *     1. k = 0, left = 0, right = 4
     *        -> if走分支3:将temp[right=4]放入到arrays[k=0]中, 此时arrays=[1, 8, 9, 11, 1, 5, 7]
     *     2. k = 1, left = 0, right = 5
     *        -> if走分支4: 将temp[left=0]放入到arrays[k=1]中, 此时arrays=[1, 3, 9, 11, 1, 5, 7]
     *     3. k = 2, left = 1, right = 5
     *        -> if走分支3: 将temp[right=5]放入到arrays[k=2]中, 此时arrays=[1, 3, 5, 11, 1, 5, 7]
     *     4. k = 3, left = 1, right = 6
     *        -> if走分支3: 将temp[right=6]放入到arrays[k=3]中, 此时arrays=[1, 3, 5, 7, 1, 5, 7]
     *     5. k = 4, left = 1, right = 7
     *        -> if走分支2: 右边的数据已经归并完了(right > height),直接将左边数据拷贝, 此时arrays=[1, 3, 5, 7, 8, 5, 7]
     *     6. k = 5, left = 2, right = 7
     *        -> if走分支2: 此时arrays=[1, 3, 5, 7, 8, 9, 7]
     *     7. k = 6, left = 3, right = 7
     *        -> if走分支2: 此时arrays=[1, 3, 5, 7, 8, 9, 11]
     *     归并排序完成,arrays已经排好序。
     * </p>
     * @param arrays
     * @param low
     * @param mid
     * @param height
     */
    private void merge(Comparable[] arrays, int low, int mid, int height) {

        // 左移动位置
        int left = low;
        // 右移动位置
        int right = mid + 1;

        // 把原数组中需要排序的元素拷贝到辅助空间中去
        for (int k = low; k <= height; k ++) {
            temp[k] = arrays[k];
        }

        for (int k = low; k <= height; k ++) {

            // 分支1: 左边已经归并完了,直接拷贝右边
            if (left > mid) {
                arrays[k] = temp[right ++];
            }
            // 分支2: 右边已经归并完了,直接拷贝左边
            else if (right > height) {
                arrays[k] = temp[left ++];
            }
            // 分支3: 右边数据小, 将小到元素放入到原数组中
            else if (less(temp[right], temp[left])) {
                arrays[k] = temp[right ++];
            }
            // 分支4: 将小到元素放入到原数组中, 左边数据小
            else {
                arrays[k] = temp[left ++];
            }

        }

    }

}
归并排序的性能分析
稳定性分析

结合前面的图和归并排序代码,可以发现,归并排序是否稳定关键在于要看合并阶段merge()函数,merge()函数将两个有序子数组合并成一个有序数组。在合并过程中,如果arrays[low, mid]和arrays[mid+1, heigh]中有相同的元素,在merge()函数中比较时走分支4,所以可以保证相同元素的先后顺序是不变的。所以归并排序是稳定排序算法。

时间复杂度分析

归并排序涉及到递归,假设对n个元素进行归并排序需要时间T(n),分解成2个子数组排序的时间都是T(n/2), merge()函数合并两个有序子数组的时间复杂度是O(n), 所以,归并排序的时间复杂度计算公式为:

T(1) = C;   (C为常数,只需要常数量级的执行时间)
T(n) = 2 * T(n/2) + n;  n > 1

根据上面公式我们可以推导出以下公式:

T(n) = 2 * T(n/2) + n
       = 2 * (2 * T(n/4) + n/2) + n = 4 * T(n/4) + 2 * n
	   = 4 * (2 * T(n/8) + n/4) + 2 * n = 8 * T(n/8) + 3n
	   ......
	   = 2^k * T(n/2^k) + k * n

最终,当n/2^k = 1时,T(n/2^k) = T(1) = C, 此时k = log(n)。我们就给k=log(n)代入到T(n) = 2^k * T(n/2^k) + k * n中,可以得到,T(n) = 2 * C * n + n * log(n)。使用大O标记法来表示就是T(n) = O(nlogn)。

转载于:https://my.oschina.net/xiaoqiyiye/blog/2247679

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值