都2020年了,据说你还不会归并排序?手把手教你手写归并排序算法

本文介绍了归并排序的基本思想,递归方法的通常写法,最后一步步手写归并排序,并对其性能进行了分析。html

基本思想

归并排序是创建在归并操做上的一种有效的排序算法,该算法是采用分治法的一个很是典型的应用。即先使每一个子序列有序,再将已有序的子序列合并,获得彻底有序的序列。这里给出一种递归形式的归并排序实现。java

递归方法的通常写法

递归方法的书写主要有三步:git

  1. 明确递归方法的功能边界;
  2. 获得递归的递推关系;
  3. 给定递归的终止条件。

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

手写归并排序

首先,明确递归方法的功能,这里咱们定义方法的功能为,给定一个数组及左右边界,方法完成数组边界内元素的排序,以下:算法

private static void mergeSort(int[] arr,int left,int right);

先假设咱们已经有了这么一个方法,不用管具体的实现。 接着,寻找递推关系,什么是递推关系呢?就是如何由子问题的求解,来获得原问题的求解,仍是举例说明,有以下的数组 原始数组 咱们将其拆分为左右两部分,以下 拆分数组 递推关系就是,假如左右两部分都已经有序了,如何使整个数组有序?这个问题其实就是给定了一个数组,数组的左半部分有序,右半部分也有序,如何使整个数组有序? 首先,定义两个指针,分别指向左侧部分起始位置和右侧部分起始位置,同时建立一个辅助数组和指向其初始位置的辅助指针 定义指针及辅助数组 接着比较,左指针和右指针所对应的元素的大小,较小的元素填充至辅助数组,同时其对应的指针和辅助指针均加1,以下: 比较并填充辅助数组 依次进行,直至某左指针指向中间位置或者右指针指向数组的末尾,此时要将将剩余的元素填充至辅助数组。全部的元素填充完成后,再将辅助数组中的元素填充回原数组便可。具体的代码以下:编程

/**
     *
     * @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);//调用写好的递归版归并排序方法
	}

至此,咱们便完成了归并排序算法的代码实现。函数

性能分析

在分析归并排序算法性能以前,先介绍几个基础的概念。 时间复杂度:一个算法执行所消耗的时间; 空间复杂度:运行完一个算法所需的内存大小; 原地排序:在排序过程当中不申请多余的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序。 非原地排序:须要利用额外的数组来辅助排序。 稳定排序:若是 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)。这个递归问题的时间复杂度能够用下面的公式来计算 递归函数的时间复杂度计算公式 这个公式可针对形如:T(n) = aT(n/b) + f(n)的递归方程进行时间复杂度求解。带入可知,归并排序的时间复杂度为O(nlogn)。此外在最坏、最佳、平均状况下归并排序时间复杂度均为O(nlogn)。 空间复杂度分析:在排序过程当中使用了一个与原数组等长的辅助数组,估空间复杂度为O(n)。 稳定性分析:由排序过程能够知道,归并排序是一种稳定排序。 是否原地排序:排序过程当中用到了辅助数组,因此是非原地排序。 本文源码已同步至github,地址:https://github.com/zhanglianchao/AllForJava/tree/master/src/algorithm/sort性能

以为文章有用的话,点赞+关注呗,好让更多的人看到这篇文章,也激励博主写出更多的好文章。 更多关于算法、数据结构和计算机基础知识的内容,欢迎扫码关注个人原创公众号「<font color=#005AB5>超悦编程</font>」。

超悦编程

原文出处:https://www.cnblogs.com/exzlc/p/12203681.html

相关文章
相关标签/搜索