排序算法总结之归并排序
一,归并排序介绍
归并排序是一个典型的基于分治的递归算法。它不断地将原数组分成大小相等的两个子数组(可能相差1),最终当划分的子数组大小为1时(下面代码第17行left小于right不成立时) ,将划分的有序子数组合并成一个更大的有序数组。为什么是有序子数组???
归并排序的递归公式:T(N) = 2T(N/2) + O(N)
从公式中可以看出:将规模为 N 的原问题分解成两个规模 N/2 的两个子问题;并且,合并这两个子问题的代价是 O(N)---[后面的 +O(N) 表示合并的代价]
二,归并排序算法分析
归并排序算法有两个基本的操作,一个是分,也就是把原数组划分成两个子数组的过程。另一个是治,它将两个有序数组合并成一个更大的有序数组。
它将数组平均分成两部分: center = (left + right)/2,当数组分得足够小时---数组中只有一个元素时,只有一个元素的数组自然而然地就可以视为是有序的,此时就可以进行合并操作了。因此,上面讲的合并两个有序的子数组,是从 只有一个元素 的两个子数组开始合并的。
合并后的元素个数:从 1-->2-->4-->8......
比如初始数组:[24,13,26,1,2,27,38,15]
①分成了两个大小相等的子数组:[24,13,26,1] [2,27,38,15]
②再划分成了四个大小相等的子数组:[24,13] [26,1] [2,27] [38,15]
③此时,left < right 还是成立,再分:[24] [13] [26] [1] [2] [27] [38] [15]
此时,有8个小数组,每个数组都可以视为有序的数组了!!!,每个数组中的left == right,从递归中返回(从19行--20行的代码中返回),故开始执行合并(第21行):
merge([24],[13]) 得到 [13,24]
merge([26],[1]) 得到[1,26]
.....
.....
最终得到 有序数组。
三,归并排序算法实现
1 public class MergeSort { 2 3 public static <T extends Comparable<? super T>> void mergeSort(T[] arr) { 4 T[] tmpArray = (T[]) new Comparable[arr.length]; 5 mergeSort(arr, tmpArray, 0, arr.length - 1); 6 } 7 8 /** 9 * 10 * @param arr an array of Comparable items 11 * @param tmpArray an array to place the merge result 12 * @param left the left-most index of the array 13 * @param right right-most index of the array 14 */ 15 private static <T extends Comparable<? super T>> void mergeSort(T[] arr, 16 T[] tmpArray, int left, int right) { 17 if (left < right) { 18 int center = (left + right) / 2; 19 mergeSort(arr, tmpArray, left, center); 20 mergeSort(arr, tmpArray, center + 1, right); 21 merge(arr, tmpArray, left, center + 1, right); 22 } 23 } 24 25 /** 26 * 27 * @param arr an array of Comparable items 28 * @param tmpArray an array to place the merge result 29 * @param leftPos the left-most index of the subarray 30 * @param rightPos the index of the start of the second half 31 * @param rightEnd the right-most index of the subarray 32 */ 33 private static <T extends Comparable<? super T>> void merge(T[] arr, 34 T[] tmpArray, int leftPos, int rightPos, int rightEnd) { 35 int leftEnd = rightPos - 1; 36 int numElements = rightEnd - leftPos + 1; 37 int tmpPos = leftPos;// 只使用tmpArray中某一部分区域 38 while (leftPos <= leftEnd && rightPos <= rightEnd) { 39 if (arr[leftPos].compareTo(arr[rightPos]) <= 0) 40 tmpArray[tmpPos++] = arr[leftPos++]; 41 else 42 tmpArray[tmpPos++] = arr[rightPos++]; 43 } 44 45 while (leftPos <= leftEnd) 46 tmpArray[tmpPos++] = arr[leftPos++];// copy rest of left half 47 while (rightPos <= rightEnd) 48 tmpArray[tmpPos++] = arr[rightPos++];// copy rest of right half 49 50 // copy tmpArray back 51 for (int i = 0; i < numElements; i++, rightEnd--) 52 arr[rightEnd] = tmpArray[rightEnd];//只拷贝当前 merge 的部分数组 53 54 /** 55 * 复制了整个数组中的所有元素 56 for(int i = 0; i < tmpArray.length; i++) 57 arr[i] = tmpArray[i]; 58 */ 59 } 60 61 //for test purpose 62 public static void main(String[] args) { 63 Integer[] arr = {24,13,26,1,2,27,38,15}; 64 mergeSort(arr); 65 for (Integer i : arr) 66 System.out.print(i + " "); 67 } 68 }
①第3行的公共方法,是对外的排序接口,首先创建一个临时数组tmpArray,用来保存合并过程中,两个子数组临时合并的结果。将tmpArray作为参数传递给递归调用的方法,而不是在执行递归调用的方法里面创建临时数组,这样可以大大地减少临时数组的创建。若在递归调用的方法里创建临时数组,每一层递归调用,都会创建一个临时数组。
②第15行的私有方法,是执行递归调用的方法。在某次具体的递归调用中,只用到了tmpArray中的某一部分空间(leftEnd 和 rightEnd之间的空间)。
③第38行while循环,比较两个子数组中的元素,谁小就把谁放到tmpArray中。
④第45行和第47行的两个while循环完成的功能是:当合并两个有序的子数组时,一个子数组中的元素已经全部放到tmpArray中去了,另一个子数组中还剩下有元素,故将剩下的所有元素直接复制到tmpArray中。
⑤第51行for循环,将本次merge完成的两个子数组复制到原数组中去。注意,它只复制本次参与合并的两个子数组中的元素。为什么要复制到原数组中去呢?因为在下一次的合并过程中,需要合并的是更大的子数组,这个更大的数组,就是由上次合并的生成的有序小数组组成的。比如:
在合并这两个数组时:[24] [13]
下一次合并的则是:[13,24] [1,26]
四,归并排序算法复杂度分析
归并排序中,用到了一个临时数组,故空间复杂度为O(N)
由归并排序的递归公式:T(N) = 2T(N/2) + O(N) 可知时间复杂度为O(NlogN)
数组的初始顺序会影响到排序过程中的比较次数,但是总的而言,对复杂度没有影响。平均情况 or 最坏情况下 它的复杂度都是O(NlogN)
此外,归并排序中的比较次数是所有排序中最少的。原因是,它一开始是不断地划分,比较只发生在合并各个有序的子数组时。
因此,JAVA的泛型排序类库中实现的就是归并排序。因为:对于JAVA而言,比较两个对象的操作代价是很大的(根据Comparable接口的compareTo方法进行比较),而移动两个对象,其实质移动的是引用,代价比较小。(排序本质上是两种操作:比较操作和移动操作)
java.util.Arrays.sort(T[] arr)使用的是归并排序
java.util.Arrays.sort(int[] arr) 使用的是快速排序
2018-11-24更新:
JDK7中使用 TimSort算法取代了原来的归并排序。优化点:①归并排序中的“分”不再直至划分到单个元素;②引入binarySort二分查找思想。
五,参考资料
排序算法总结之插入排序
排序算法总结之堆排序
排序算法总结之快速排序
各种排序算法的总结
排序算法总结之归并排序相关推荐
- 图解排序算法(四)之归并排序
图解排序算法(四)之归并排序 基本思想 归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide) ...
- js排序算法详解-归并排序
js系列教程5-数据结构和算法全解 js排序算法详解-归并排序 归并排序其实可以类比二分法,二分法其实就是二等分的意思,简而言之就是不断和新序列的中间值进行比较.归并排序似乎有异曲同工之妙,什么意思呢 ...
- java 排序算法总结,Java排序算法总结之归并排序
本文实例讲述了Java排序算法总结之归并排序.分享给大家供大家参考.具体分析如下: 归并操作(merge),也叫归并算法,指的是将两个已经排序的序列合并成一个序列的操作.和快速排序类似,让我们一起来看 ...
- 排序算法之——二路归并排序
排序算法之--二路归并排序 二路归并排序的思想: 一次排序过程,将已经各自有序的两个段的数据合并一个段,并且合并后依旧有序 开始,我们认为单个数据是有序的,一个数据就是一个段,一次排序之后,两个数据就 ...
- 【Java】排序算法 之 【归并排序】 总结
目录 1.合并两个有序数组函数 2. 归并排序 3. 非递归实现归并排序 1.合并两个有序数组函数 在学习归并排序之前,我们需要掌握合并两个有序数组为一个有序数组的函数是如何实现的 /*** 合并两个 ...
- PHP实现归治算法,PHP排序算法系列之归并排序详解
归并排序 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用.将已有序的子序列合并,得到完全有序的序 ...
- 排序算法二:归并排序(Merge sort)
归并排序(Merge sort)用到了分治思想,即分-治-合三步,算法平均时间复杂度是O(nlgn). (一)算法实现 1 private void merge_sort(int[] array, i ...
- 排序算法系列:归并排序算法
概述 上一篇我们说了一个非常简单的排序算法--选择排序.其复杂程序完全是冒泡级的,甚至比冒泡还要简单.今天要说的是一个相对比较复杂的排序算法--归并排序.复杂的原因不仅在于归并排序分成了两个部分进行解 ...
- 排序算法研究之归并排序(Merge sort)
前面几个小节,我们分别介绍了冒泡排序,插入排序,直接快速排序 ,选择排序 , 希尔排序, 堆排序本节,我们介绍基于归并操作的归并排序. 1.算法思想 归并排序是建立在归并操作上的一种有效的排序算法.该 ...
最新文章
- 如何写出符合Python审美的代码风格?
- 使用指针判断数组是否为上三角矩阵
- 瑞星个人安全产品又添新丁
- su 与 sudo 区别
- NoSQL Databases - CouchDB
- 【渝粤题库】国家开放大学2021春2223物业管理财税基础题目
- Python异步非阻塞IO多路复用Select/Poll/Epoll使用
- linux中top和ps的内存区别,linux - top与ps间的区别
- php 写一个大富翁游戏,C++大富翁代码 现在要设计一个类似“大富翁”的游戏:有一条由20个格子组成的 联合开发网 - pudn.com...
- 设置网页打开默认全屏_微信公众号里的视频不能进行全屏播放的解决方法
- 实战:基于RabbitMQ的TTL以及死信队列,实现延迟付款,手动补偿案例
- 为什么python发展的好_为什么Python发展这么快,有哪些优势?
- python ----json数据处理
- WebRTC 概念介绍--一篇读懂source、track、sink、mediastream
- ip地址详解(深入网络层分析)
- [转帖]一些不该被埋没的好歌【全版】【2】
- esim卡与ms卡的区别_什么是eSIM,它与SIM卡有何不同?
- java 获取 ip mac_Java获取客户端ip mac地址
- QBQ!问题背后的问题
- ‘isVNode‘ is not exported by