数据结构与算法——归并排序: 数组&链表&递归&非递归解法全家桶

本文详细介绍了归并排序算法,包括其工作原理、步骤和时间复杂度。分别展示了数组和链表的递归与非递归实现,针对每种实现方式提供了力扣平台的执行时间和内存消耗情况。归并排序始终保持O(nlogn)的时间复杂度,空间复杂度在数组中为O(n),而在链表中为O(1)。
摘要由CSDN通过智能技术生成

原文链接:https://jiang-hao.com/articles/2020/algorithms-algorithms-merge-sort.html

算法介绍

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:

  • 自上而下的递归:它从树的顶端开始,然后向下操作,每次操作都问同样的问题(我需要做什么来排序这个数组?)并回答它(分成两个子数组,进行递归调用,合并结果),直到我们到达树的底部。

    Picture2.png

  • 自下而上的迭代:不需要递归。它直接从树的底部开始,然后通过遍历这些片段再将它们合并起来。

    Picture1.png

在《数据结构与算法 JavaScript 描述》中,作者给出了自下而上的迭代方法。但是对于递归法,作者却认为:

However, it is not possible to do so in JavaScript, as the recursion goes too deep for the language to handle.
然而,在 JavaScript 中这种方式不太可行,因为这个算法的递归深度对它来讲太深了。

和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。

归并排序分为三个过程:

  1. 将数列划分为两部分(在均匀划分时时间复杂度为 );
  2. 递归地分别对两个子序列进行归并排序;
  3. 合并两个子序列。

不难发现,归并排序的核心是如何合并两个子序列,前两步都很好实现。

其实合并的时候也不难操作。注意到两个子序列在第二步中已经保证了都是有序的了,第三步中实际上是想要把两个 有序 的序列合并起来。

算法步骤

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
  4. 重复步骤 3 直到某一指针达到序列尾;
  5. 将另一序列剩下的所有元素直接复制到合并序列尾。

img

代码实现

数组实现时间复杂度O(NlogN),空间复杂度O(N)

递归实现一:每次归并时都创建一个辅助数组
public static int[] sort(int[] nums) {
   
    // 对数组进行拷贝,不改变参数内容
    int[] arr = Arrays.copyOf(nums, nums.length);

    if (arr.length<2) return arr;

    int middle = (int) Math.floor(arr.length >> 1);

    int[] left = Arrays.copyOfRange(arr, 0, middle);
    int[] right = Arrays.copyOfRange(arr, middle, arr.length);

    return merge(sort(left), sort(right));
}

public static int[] merge(int[] left, int[] right) {
   
    // 创建一个辅助数组存储归并结果
    int[] result = new int[left.length+right.length];
    int i=0, j=0;
    while (i+j < result.length) {
   
        // 右侧数组全都转存完时,直接将左侧数组剩余的元素转存到结果数组
        if (j==right.length) {
   
            result[i+j] = left[i++];
        }
        // 左侧数组全都转存完时,直接将右侧数组剩余的元素转存到结果数组
        else if (i==left.length) {
   
            result[i+j] = right[j++];
        }
        // 否则,将两个子数组当前元素中较小的那个转存到结果数组中
        else result[i+j] = left[i]<=right[j]? left[i++]: right[j++];
    }
    return result;
}

力扣运行结果:

执行用时:10 ms, 在所有 Java 提交中击败了30.97%的用户

内存消耗:44.2 MB, 在所有 Java 提交中击败了99.55%的用户

提交时间 提交结果 运行时间 内存消耗 语言
几秒前 通过 10 ms 43.8 MB
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值