基本概念

归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并

算法描述

把长度为n的输入序列分成两个长度为n/2的子序列;
对这两个子序列分别采用归并排序;
将两个排序好的子序列合并成一个最终的排序序列。
归并排序.gif

代码实现

  1. package sort;
  2. import java.util.Arrays;
  3. public class MergeSort {
  4. public static void main(String[] args) {
  5. //构建测试用例
  6. //功能测试:正常数组
  7. //边界值测试:仅含有一个元素的数组等
  8. //性能测试:数据量大的情况
  9. //异常情况:空数组等
  10. int[] test = {23,4456,66,1,54,345,68,3,6,23,567};
  11. System.out.println(Arrays.toString(mergeSort(test)));
  12. }
  13. public static int[] mergeSort(int[] array){
  14. if(array.length<2){
  15. return array;
  16. }
  17. int mid = array.length/2;
  18. int[] left = Arrays.copyOfRange(array,0,mid);
  19. int[] right = Arrays.copyOfRange(array,mid,array.length);
  20. return merge(mergeSort(left),mergeSort(right));
  21. }
  22. public static int[] merge(int[] left,int[] right){
  23. int[] result = new int[left.length + right.length];
  24. //if-else判断方式
  25. for(int index = 0,i=0,j=0;index< result.length;index++){
  26. if(i>= left.length){
  27. result[index] = right[j++];
  28. }else if(j>= right.length){
  29. result[index] = left[i++];
  30. }else if(left[i]<right[j]){
  31. result[index] = left[i++];
  32. }else{
  33. result[index] = right[j++];
  34. }
  35. }
  36. //while判断方式
  37. int index = 0, i = 0, j = 0;
  38. while (i < left.length && j < right.length) {
  39. result[index++] = left[i] < right[j] ? left[i++] : right[j++];
  40. }
  41. while (i < left.length)
  42. result[index++] = left[i++];
  43. while (j < right.length)
  44. result[index++] = right[j++];
  45. return result;
  46. }
  47. }

归并排序的非递归实现
image.png

归并排序与快速排序的比较

归并排序是稳定的排序.即相等的元素的顺序不会改变.如输入记录 1(1) 3(2) 2(3) 2(4) 5(5) (括号中是记录的关键字)时输出的 1(1) 2(3) 2(4) 3(2) 5(5) 中的2 和 2 是按输入的顺序.这对要排序数据包含多个信息而要按其中的某一个信息排序,要求其它信息尽量按输入的顺序排列时很重要。归并排序的比较次数小于快速排序的比较次数,移动次数一般多于快速排序的移动次数。

算法分析

归并排序比较占用内存,但却是一种效率高且稳定的算法。
改进归并排序在归并时先判断前段序列的最大值与后段序列最小值的关系再确定是否进行复制比较。如果前段序列的最大值小于等于后段序列最小值,则说明序列可以直接形成一段有序序列不需要再归并,反之则需要。所以在序列本身有序的情况下时间复杂度可以降至O(n)
TimSort可以说是归并排序的终极优化版本,主要思想就是检测序列中的天然有序子段(若检测到严格降序子段则翻转序列为升序子段)。在最好情况下无论升序还是降序都可以使时间复杂度降至为O(n),具有很强的自适应性。

最好时间复杂度 最坏时间复杂度 平均时间复杂度 空间复杂度 稳定性
传统归并排序 O(n_log_n) O(n_log_n) O(n_log_n) T(n) 稳定
改进归并排序 O(n) O(n_log_n) O(n_log_n) T(n) 稳定
TimSort O(n) O(n_log_n) O(n_log_n) T(n) 稳定

时间复杂度推算

(1)合并两个已排序的表的时间是线性的,最多需要进行N-1次比较,其中N是元素的个数
(2)当N=1时,归并排序所用时间为常数,记为1.
基于上述条件及归并排序的概念,得到如下方程式:
T(1)=1
T(N)=2T(N/2)+N
两边同除以N,得到方程:
归并排序 - 图3
叠缩求和可知: T(N) = NlogN + N = O(NlogN)

算法优劣

归并排序的运行时间为O(NlogN),但是他有一个明显的问题,即合并两个已排序的表用到线性附加内存.在整个算法中还要花费将数据拷贝到临时数组再拷贝回来的一些附加工作,减缓了排序的速度
与其他O(NlogN)排序算法相比,归并排序的运行时间严重依赖于比较元素和在数组中移动元素的相对开销.这些开销是语言相关的.
在Java中,当执行一次泛型排序(使用comparator)时,进行一次元素比较可能时昂贵的,但是移动元素是省时的,归并排序使用所有流行的排序算法中最少的比较次数.因此是java的通用排序算法中的上好选择.事实上,他就是标准java类库中泛型排序所使用的算法.(C++泛型使用快速排序)
Java中,快速排序作为基本类型的标准库排序.这里,比较和数据移动的开销是类似的.因此使用少得多的数据移动足以补偿那些附加的比较而且还有盈余