• 《排序算法系列6》归并排序


    1 思想

    归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

    2 举例

    可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。

    合并相邻有序子序列

    再来看看阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。

    归并排序动态演示

     

    3 代码实现

     1     public static void main(String[] args) {
     2         int[] arr = { 8, 4, 5, 7, 1, 3, 6, 2 };
     3         int[] temp = new int[arr.length];// 归并排序需要一个额外的空间
     4         mergeSort(arr, 0, arr.length - 1, temp);
     5         System.out.println("归并排序后的结果" + Arrays.toString(arr));
     6     }
     7     
     8     /**
     9        *  通过递归实现数组的分阶段  然后调用数组合并方法
    10       * 从而实现归并排序的分而治之思想
    11      * @param arr   需要排序的数组
    12      * @param left  排序数组的开始索引
    13      * @param right 排序数组的结束索引
    14      * @param temp  辅助数组,用于接受排序的结果
    15      * void
    16      */
    17     public static void mergeSort(int[] arr, int left, int right, int[] temp) {
    18         if (left < right) {
    19             int mid = (left + right) / 2;// 中间索引
    20             // 向左递归进行分解
    21             mergeSort(arr, left, mid, temp);
    22             // 向右递归进行分解
    23             mergeSort(arr, mid + 1, right, temp);
    24             // 合并
    25             merge(arr, left, mid, right, temp);
    26         }
    27     }
    28 
    29     /**
    30      *  治阶段: 将分开的2个数组合并s 
    31      * 
    32      * @param arr   排序的原始数组
    33      * @param left  左边有序数组
    34      * @param mid   中间索引
    35      * @param right 右边索引
    36      * @param temp  中转的数组
    37      */
    38     public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
    39         int i = left;// 左边有序序列的初始索引
    40         int j = mid + 1;// 右边有序序列的初始索引
    41         int t = 0; // 指向temp数组的当前索引
    42 
    43         // 步骤一:
    44         // 先把左右两边的数据按照规则填充到temp数组
    45         // 直接左右两边的数组 有一边处理完毕为止
    46         while (i <= mid && j <= right) {
    47             // 如果左边的有序序列的当前元素 小于等于右边有序序列的当前元素
    48             // 把左边的元素添加到temp数组
    49             if (arr[i] <= arr[j]) {
    50                 temp[t] = arr[i];
    51                 i++;
    52                 t++;
    53             } else {
    54                 temp[t] = arr[j];
    55                 j++;
    56                 t++;
    57             }
    58         }
    59 
    60         // 步骤二:
    61         // 把有剩余的数组一边的数据依次填充到temp
    62         while (i <= mid) {
    63             temp[t] = arr[i];
    64             t++;
    65             i++;
    66         }
    67         while (j <= right) {
    68             temp[t] = arr[j];
    69             t++;
    70             j++;
    71         }
    72 
    73         // 步骤三:
    74         // 把temp拷贝到arr
    75         //初始化t 
    76         t = 0;
    77         int tempLeft = left;
    78         // 注意 并不是每次都拷贝所有 因为不需要拷贝temp 只需要拷贝当前的排好序的数组
    79         while (tempLeft <= right) {
    80             arr[tempLeft] = temp[t];
    81             t++;
    82             tempLeft++;
    83         }
    84     }

    4 时间复杂度

    假如n个元素使用归并排序的时间复杂度为T(n),那么由于归并排序使用的是分治思想,T(n)=2*T(n/2)+n,其中n就是两个子区间合并的时间复杂度,这个从合并函数可以看出来。可以推导出以下公式:

                            T(1) = C;   n=1 时,只需要常量级的执行时间,所以表示为 C。
                             T(n) = 2*T(n/2) + n; n>1

    经过进一步推导,可以得到T(n)=2^k * T(n/2^k) + k * n,我们假设T(1)=T(n/2^k),也就是说当n/2^k个元素的进行归并排序,达到递归终止条件时,n/2^k=1,得到:k=logn

    于是:

                                                 T(n)=Cn+nlog2n

    归并排序的时间复杂度就是O(nlogn),跟数组的有序度其实并没有什么关系,是非常稳定的时间复杂度。

     5 时间复杂度速度测试

     1   // 归并排序
     2     public static void main(String[] args) {
     3         speedTest(8000000);
     4     }
     5 
     6     /**
     7        * 创建一个随机数组 然后调用排序方法 得到时间
     8      * 
     9      * @param number 创建的随机数组个数
    10      */
    11     public static void speedTest(int number) {
    12         int[] arr = new int[number];
    13         int[] arr2 = new int[number];
    14         for (int i = 0; i < arr.length; i++) {
    15             arr[i] = (int) (Math.random() * 800000);
    16         }
    17 
    18         SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    19         Date date1 = new Date();
    20         String time1 = simpleDateFormat.format(date1);
    21         System.out.println("排序前的时间为:" + time1);
    22 
    23         // 调用上面的归并排序方法
    24         mergeSort(arr, 0, arr.length-1, arr2);
    25 
    26         Date date2 = new Date();
    27         String time2 = simpleDateFormat.format(date2);
    28         System.out.println("排序后的时间为:" + time2);
    29     }

    归并排序速度测试结果

    8万个数据测试结果大约需要不到1秒

     80万个数据测试结果大约需要不到

     800万个数据测试结果大约需要2秒

    8000万个数据测试结果大约16秒

  • 相关阅读:
    Ajax数据爬取
    数据存储之非关系型数据库存储----MongoDB存储(Python操作)
    数据存储之关系型数据库存储---MySQL存储(Python操作)
    数据存储之文件存储
    使用pyquery
    使用Beautiful Soup
    使用XPath
    正则表达式和python中的re模块
    Android优化之ViewPager的懒加载
    开源框架Slidingmenu的基本使用
  • 原文地址:https://www.cnblogs.com/wangxiucai/p/12679224.html
Copyright © 2020-2023  润新知