zoukankan      html  css  js  c++  java
  • 《排序算法系列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秒

  • 相关阅读:
    POJ 2236 Wireless Network(并查集)
    POJ 2010 Moo University
    POJ 3614 Sunscreen(贪心,区间单点匹配)
    POJ 2184 Cow Exhibition(背包)
    POJ 1631 Bridging signals(LIS的等价表述)
    POJ 3181 Dollar Dayz(递推,两个long long)
    POJ 3046 Ant Counting(递推,和号优化)
    POJ 3280 Cheapest Palindrome(区间dp)
    POJ 3616 Milking Time(dp)
    POJ 2385 Apple Catching(01背包)
  • 原文地址:https://www.cnblogs.com/wangxiucai/p/12679224.html
Copyright © 2011-2022 走看看