归并排序时间复杂度_听说你还不会归并排序?

c78760d3c8b1d11553d0a48ffa6c603e.gif

85c825c84abb145df525205954006578.png

作者 | 超悦人生 责编 | 郭芮 本文介绍了归并排序的基本思想,递归方法的一般写法,最后一步步手写归并排序,并对其性能进行了分析。

9812a7dde9f45409ad1a460f17e0b818.png

基本思想 归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。即先使每个子序列有序,再将已有序的子序列合并,得到完全有序的序列。这里给出一种递归形式的归并排序实现。

5f4ddbd5e59b6978eeaae646de6b1fdb.png

递归方法的一般写法 递归方法的书写主要有三步:
  • 明确递归方法的功能边界;

  • 得到递归的递推关系;

  • 给定递归的终止条件。

递归方法均可按照这三步进行,切忌不要陷入递归实现的细节中。下面以归并排序算法的书写为例,来谈一下递归方法的具体写法。

72da9c3689a6ceb9a684f13db4f29433.png

手写归并排序 首先,明确递归方法的功能,这里我们定义方法的功能为,给定一个数组及左右边界,方法完成数组边界内元素的排序,如下:
private static void mergeSort(int[] arr,int left,int right);
先假设我们已经有了这么一个方法,不用管具体的实现。 接着,寻找递推关系,什么是递推关系呢?就是如何由子问题的求解,来得到原问题的求解,还是举例说明,有如下的数组: 5375514caf061cb9b163ffea01c76c21.png 原始数组 我们将其拆分为左右两部分,如下: 912cf0c0c99eb96438ac7af93f8701bc.png 拆分数组 递推关系就是,假如左右两部分都已经有序了,如何使整个数组有序?这个问题其实就是给定了一个数组,数组的左半部分有序,右半部分也有序,如何使整个数组有序? 首先,定义两个指针,分别指向左侧部分起始位置和右侧部分起始位置,同时创建一个辅助数组和指向其初始位置的辅助指针: 9502d11b8565ede40e26bc5c06521365.png 定义指针及辅助数组 接着比较,左指针和右指针所对应的元素的大小,较小的元素填充至辅助数组,同时其对应的指针和辅助指针均加1,如下: ff5b474c66564bce421274074c38d1dc.png 比较并填充辅助数组 依次进行,直至某左指针指向中间位置或者右指针指向数组的末尾,此时要将将剩余的元素填充至辅助数组。所有的元素填充完成后,再将辅助数组中的元素填充回原数组即可。具体的代码如下:
/**     *     * @param arr 要合并的数组     * @param left 左边界     * @param mid 中间的分界     * @param right 右边界     */    private static void merge(int[] arr,int left,int mid,int right){        int[] helpArr = new int[right - left + 1];//首先定义一个辅助数组        int lPoint = left;//左指针        int rPoint = mid  + 1;//右指针        int i = 0;//辅助指针        while(lPoint <= mid && rPoint <= right){//比较并填充辅助数组            if(arr[lPoint] <=  arr[rPoint])                helpArr[i++] =  arr[lPoint++];            else                helpArr[i++] =  arr[rPoint++];        }        while(lPoint <= mid){//将剩余元素填充至辅助数组            helpArr[i++] =  arr[lPoint++];        }        while(rPoint <= right){            helpArr[i++] =  arr[rPoint++];        }        for(int j = 0;j < helpArr.length;j ++){//将辅助数组中的元素回填至原数组            arr[left + j] = helpArr[j];        }    }
最后,确定终止条件,一般是数组为空或者数组中只有一个元素,返回即可。 现在我们可以写出整个归并排序的代码了,如下:
private static void mergeSort(int[] arr,int left,int right){        if(arr == null || right == left)//终止条件            return ;        int mid = left + (right - left) / 2;//确定分割的边界        mergeSort(arr,left,mid);//对左半部分调用递归方法,使其有序        mergeSort(arr,mid + 1,right);//对右半部分调用递归方法,使其有序        merge(arr,left,mid,right);//合并左右两部分,使整个数组有序    }
为了保证形式的统一,再对函数进行一下封装,如下,这就是我们的归并排序了。
    /**     * 归并排序算法     * @param arr     */    public static void mergeSort(int[] arr){        mergeSort(arr,0,arr.length - 1);//调用写好的递归版归并排序方法    }
至此,我们便完成了归并排序算法的代码实现。

d4ddbe37b6492a6a02028e5fa8bd4800.png

性能分析 在分析归并排序算法性能之前,先介绍几个基础的概念。
  • 时间复杂度:一个算法执行所消耗的时间;

  • 空间复杂度:运行完一个算法所需的内存大小;

  • 原地排序:在排序过程中不申请多余的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序。

  • 非原地排序:需要利用额外的数组来辅助排序。

  • 稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 仍然在 b 的前面,则为稳定排序。

  • 非稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 可能不在 b 的前面,则为非稳定排序。

下面我们分析下归并排序算法的性能。

首先是时间复杂度。归并排序算法在排序时首先将问题进行分解,然后解决子问题,再合并,所以总时间=分解时间+解决子问题时间+合并时间。分解时间就是把一个数组分解为左右两部分,时间为一常数,即O(1);解决子问题时间是两个递归方法,把一个规模为n的问题分成两个规模分别为n/2的子问题,时间为2T(n/2);合并时间复杂度为O(n)。所以总时间T(n)=2T(n/2)+O(n)。这个递归问题的时间复杂度可以用下面的公式来计算 17b3e2b05b9f9ca32267fdd3bba7d6e1.png 递归函数的时间复杂度计算公式 这个公式可针对形如:T(n) = aT(n/b) + f(n)的递归方程进行时间复杂度求解。带入可知,归并排序的时间复杂度为O(nlogn)。此外在最坏、最佳、平均情况下归并排序时间复杂度均为O(nlogn)。
  • 空间复杂度分析:在排序过程中使用了一个与原数组等长的辅助数组,估空间复杂度为O(n)。

  • 稳定性分析:由排序过程可以知道,归并排序是一种稳定排序。

  • 是否原地排序:排序过程中用到了辅助数组,所以是非原地排序。

本文源码已同步至github,地址:https://github.com/zhanglianchao/AllForJava/tree/master/src/algorithm/sort。

声明:本文为作者投稿,版权归其个人所有。

【END】

a2737c957e3c7a10e7f25c17c8425222.png

更多精彩推荐

☞Go 之禅

☞鹿晗都有 AI 粉了,为什么 AI 换脸剧的效果还这么渣?

☞循环智能杨植麟:“人机耦合”将是对话语义应用的新趋势!

☞曾遭周鸿祎全网封杀的360猛将:草根打工到36岁身家上亿的逆袭!

☞详Kubernetes在边缘计算领域的发展

☞原来疫情发生后,全球加密社区为了抗击冠状病毒做了这么多事情!

☞一文读懂“情感计算”在零售中的应用发展

f275e09b65f3f45da1c8e32d9e47bfb4.png 你点的每个“在看”,我都认真当成了喜欢
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值