zoukankan      html  css  js  c++  java
  • 小白初识 归并排序(MergeSort)

    归并排序是一种典型的用分治的思想解决问题的排序方式。

    它的原理就是:将一个数组从中间分成两半,对分开的两半再分成两半,直到最终分到最小的单位(即单个元素)的时候,

    将已经分开的数据两两合并,并且在合并的同时进行排序(先分解,再合并)。

    将一个大的问题分而治之,拆分成若干个小问题,这就是分治的思想。

    拆分不成问题,但是合并的时候稍微麻烦一些。合并的时候需要对合并的数据挨个儿排序。

    我用Java实现了归并排序:

     1 package com.structure.sort;
     2 
     3 /**
     4  * @author zhangxingrui
     5  * @create 2019-01-24 21:22
     6  **/
     7 public class MergeSort {
     8 
     9     public static void main(String[] args) {
    10         int[] numbers = {9, 1, 2, 8, 7, 3, 6, 4, 3, 5, 0, 9, 19, 39, 25, 34, 17, 24, 23, 34, 20};
    11         // 归并排序借助递归来实现,重要的是要找到递归的终结条件(不然容易发生堆栈异常)
    12         // 递推公式:mergeSort(p...r) = merge(p, q) + merge(q+1, r)
    13         // 终结条件:p >= r
    14         mergeSort(numbers, 0, numbers.length - 1);
    15         for (int number : numbers) {
    16             System.out.println(number);
    17         }
    18     }
    19 
    20     /**
    21      * @Author: xingrui
    22      * @Description: 归并排序
    23      * @Date: 21:26 2019/1/24
    24      */
    25     private static void mergeSort(int[] numbers, int p, int r){
    26         if(p >= r)
    27             return;
    28         int q = (p + r) / 2;
    29         mergeSort(numbers, p, q);
    30         mergeSort(numbers, q + 1, r);
    31         merge(numbers, p, q, r);
    32     }
    33 
    34     /**
    35      * @Author: xingrui
    36      * @Description: 合并数组
    37      * @Date: 21:35 2019/1/24
    38      */
    39     private static void merge(int[] numbers, int p, int q, int r){
    40         int[] temp = new int[r - p + 1];
    41         int i = p;
    42         int j = q + 1;
    43         int k = 0;
    44 
    45         while (i <= q && j <= r){
    46             if(numbers[i] <= numbers[j]){
    47                 temp[k++] = numbers[i++];
    48             }else{
    49                 temp[k++] = numbers[j++];
    50             }
    51         }
    52 
    53         while (i <= q)
    54             temp[k++] = numbers[i++];
    55 
    56         while (j <= r)
    57             temp[k++] = numbers[j++];
    58 
    59 
    60         for (int number : temp) {
    61             numbers[p++] = number;
    62         }
    63     }
    64 
    65 }

    像上面的代码看到的,拆分的时候都是从中间拆分:

    1 private static void mergeSort(int[] numbers, int p, int r){
    2         if(p >= r)
    3             return;
    4         int q = (p + r) / 2;
    5         mergeSort(numbers, p, q);
    6         mergeSort(numbers, q + 1, r);
    7         merge(numbers, p, q, r);
    8     }

    所以这里每次都要(p + r) / 2 得到我们想要的中间位置。

    代码实现起来很容易,那么我们再来分析一下归并排序:

    1.归并排序是稳定的排序算法吗(即数组中有相同的元素,在排序结束时候,相同的元素的前后关系并没有发生变化)?

    2.归并排序是原地排序吗(空间复杂度为O(1)就可以叫做原地排序)?

    3.归并排序的时间复杂度怎么计算?

    解答:

    1.归并排序是稳定的排序的算法。我们可以看看发生元素位置交换的地方:

    while (i <= q && j <= r){
                if(numbers[i] <= numbers[j]){
                    temp[k++] = numbers[i++];
                }else{
                    temp[k++] = numbers[j++];
                }
            }

    i比j小,所以numbers[i]永远在numbers[j]的前面,当numbers[i] = numbers[j]的时候,我们是把numbers[i]放到了temp里面,

    所以归并排序下来,相同的元素的前后关系没有发生变化。

     

    2.归并排序不是原地排序。这个很明显,在merge过程中需要申请一个temp数组来临时存储数据,而这个temp数组大小不确定。

    3.归并排序的时间复杂度是O(nlogn)。这个就需要推导一下了:

    首先我们得知道归并排序怎么用表达式表达出来:

    T(p...r) = T(p...q) + T(q+1...r) + k,其中k是合并两段数组需要的时间。

    假设我们对n个元素进行排需要的时间为:T(n),那么对n/2个数组排序需要的时间就是T(n/2),合并数组的时间复杂度就是O(n)

    我们可以得出以下公式:

    T(1) = C (C表示一个常量级别的时间)

    T(n) = 2 * T(n/2) + n

    以此类推:

    T(n) = 2*T(n/2) + n
    = 2*(2*T(n/4) + n/2) + n = 4*T(n/4) + 2*n
    = 4*(2*T(n/8) + n/4) + 2*n = 8*T(n/8) + 3*n
    = 8*(2*T(n/16) + n/8) + 3*n = 16*T(n/16) + 4*n
    ......
    = 2^k * T(n/2^k) + k * n
    ......

    得到了2^k * T(n/2^k) + k * n,那么当T(n/2^k) = 1的时候,即n/2^k = 1,k=log2n,

    再将k值代入2^k * T(n/2^k) + k * n中可得:T(n) = Cn + nlog2n,根据时间复杂的计算原则,取大的数nlog2n,

    所以归并排序的时间复杂的就是O(nlogn),并且从上面的代码中也可以看到对于归并排序而言,它的执行效率与

    要执行排序的数组的有序度无关,即最大最小平均时间复杂度都是O(nlogn)。

  • 相关阅读:
    “同形异义字”钓鱼攻击
    研发管理101军规#001 两周迭代,形成团队持续习惯
    全新 PingCode 正式发布
    Python基础数据类型——tuple浅析
    Python基础变量类型——List浅析
    有了这个神器,快速告别垃圾短信邮件
    零基础打造一款属于自己的网页搜索引擎
    一篇文章教会你使用Python网络爬虫下载酷狗音乐
    趣味解读Python面向对象编程 (类和对象)
    上古神器Gvim--从入门到精通
  • 原文地址:https://www.cnblogs.com/alinainai/p/10336188.html
Copyright © 2011-2022 走看看