• 排序算法总结之归并排序


    一,归并排序介绍

    归并排序是一个典型的基于分治的递归算法。它不断地将原数组分成大小相等的两个子数组(可能相差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二分查找思想。

    五,参考资料

    排序算法总结之插入排序

     排序算法总结之堆排序

    排序算法总结之快速排序

    各种排序算法的总结

  • 相关阅读:
    android cocos2d-x视频
    Android OpenGL 学习笔记 --开始篇
    Nginx配置详解
    扩展RBAC用户角色权限设计方案
    几种序列化与Get、Set方法的关系
    Kettle大量数据快速导出的解决方案(利用SQL导出百万级数据,挺快的)
    SpringBoot 标准集成MyBatis的2种方式
    Apache Commons io FileUtils 详解
    SpringBoot在工具类中读取配置文件(ClassPathResource)
    利用guava封装RateLimiter 令牌桶算法(AOP实现)
  • 原文地址:https://www.cnblogs.com/hapjin/p/5518921.html
Copyright © 2020-2023  润新知