算法 - 归并排序(Merge Sort)

  • 1954年由约翰·冯·冯诺依曼(John von Nenumann)首次提出
    在这里插入图片描述
  • 执行流程
  1. 不断地将当前序列平均分割成2个子序列(直到不能分割(序列中只有1个元素))
  2. 不断地将2个子序列合并成一个有序序列(直到最终只剩下1个有序序列)
    在这里插入图片描述

归并排序 - divide实现

// 对 [begin, end) 范围的数据进行归并排序
private void sort(int begin, int end) {
    if (end - begin < 2) return;
    int mid = (begin + end) >> 1;
    sort(begin, mid);
    sort(mid, end);
    merge(begin, mid, end);
}

归并排序 - merge实现

在这里插入图片描述

  • 需要merge的2租序列存在于同一个数组中,并且是挨在一起的
    在这里插入图片描述
  • 为了更好地完成merge操作,最好将其中1组序列备份出来,比如[begin, mid)
    在这里插入图片描述
  • 实例步骤
    在这里插入图片描述
  • 实例步骤-左边先结束
    在这里插入图片描述
  • 实例步骤-右边先结束
    在这里插入图片描述
// 将 [begin, mid) 和 [mid, end) 范围的序列合并成一个有序的序列
private void merge(int begin, int mid, int end) {
    int li = 0, le = mid - begin; // 左边的数组(基于leftArray)
    int ri = mid, re = end; // 右边的数组(基于array)
    int ai = begin; //array的索引
    for (int i = li; i < le; i++) { // 拷贝左边数组到leftarray
        leftArray[i] = array[begin + i];
    }
    while (li < le) { // 左边数组比较完毕
        if (ri < re && cmp(array[ri], leftArray[li] < 0) {
            array[ai++] = array[ri++]; //拷贝右边数组到array
        } else {
            array[ai++] = leftArray[li++]; // 拷贝左边数组到array
        }
    } // cmp位置改为 <= 会失去稳定性
}

归并排序 - 复杂度分析

  • 归并排序花费的时间
T(n) = 2 * T(n/2) + O(n) // 左边sort + 右边sort + merge
T(1) = O(1)
T(n)/n = T(n/2)/(n/2) + O(1)S(n) = T(n)/n
S(1) = O(1)
S(n) = S(n/2) + O(1) = S(n/4) + O(2) = S(n/8) + O(3) = S(n/2^k) + O(k) = S(1) + O(logn) = O(logn)
T(n) = n * S(n) = O(nlogn)
  • 由于归并排序总是平均分割子序列,所以最好、最坏、平均时间复杂度都是O(nlogn),属于稳定排序
  • 从代码中不难看出:归并排序的空间复杂度是O(n/2 + logn) = O(n)
  • n/2 用于临时存放左边数组,logn是因为递归调用

常见的递推式与复杂度

在这里插入图片描述

最终版本 - 递归

private int[] leftArray;
public int[] sortArray(int[] nums) {
    leftArray = new int[nums.length >> 1];
    sort(nums, 0, nums.length);
    return nums;
}
// 对 [begin, end) 范围的数据进行归并排序
private void sort(int[] array, int begin, int end) {
    if (end - begin < 2) return;
    int mid = (begin + end) >> 1;
    sort(array, begin, mid);
    sort(array, mid, end);
    merge(array, begin, mid, end);
}
// 将 [begin, mid) 和 [mid, end) 范围的序列合并成一个有序的序列
private void merge(int[] array, int begin, int mid, int end) {
    int li = 0, le = mid - begin; // 左边的数组(基于leftArray)
    int ri = mid, re = end; // 右边的数组(基于array)
    int ai = begin; //array的索引
    for (int i = li; i < le; i++) { // 拷贝左边数组到leftarray
        leftArray[i] = array[begin + i];
    }
    while (li < le) { // 左边数组比较完毕
        if (ri < re && array[ri] < leftArray[li]) { // 改为 <= 会失去稳定性
            array[ai++] = array[ri++]; //拷贝右边数组到array
        } else {
            array[ai++] = leftArray[li++]; // 拷贝左边数组到array
        }
    } 
}

最终版本 - 非递归

class Solution {
    public int[] sortArray(int[] nums) {
        for (int i = 1; i < nums.length; i = i<<1) {
            MergePass(nums, i, nums.length);
        }
        return nums;
    }
    private void MergePass(int[] nums, int k, int len) {
        int i = 0;
        while (i + (k << 1) <= len) {
            merge(nums, i, i + k, i + (k<<1));
            i += (k<<1);
        } 
        if (i + k < len) {
            merge(nums, i, i + k, len);
        }
    }
	private void merge(int[] array, int begin, int mid, int end) {
        int[] tmp = new int[end - begin];
        int li = begin;
        int ri = mid;
        int ai = 0;
        while (li < mid && ri < end) {
            if (array[ri] < array[li])
                tmp[ai++] = array[ri++];
            else
                tmp[ai++] = array[li++];
        }
        while (li < mid) 
            tmp[ai++] = array[li++];
        while (ri < end) 
            tmp[ai++] = array[ri++];
        for (int i = 0; i < tmp.length; i++) {
            array[begin + i] = tmp[i];
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值