[Day18] CH10:排序大家族——合并排序法

今天要介绍的是我们学的最後一个排序法——合并排序法(Merge Sort)。

合并排序法

分成切割与合并两个部分:

  • 切割

    将数列对分成左子数列、右子数列。分别对左子数列、右子数列做前一个步骤(递回 Recursive)。

    直到左子数列、右子数列被分割成只剩一个元素为止,将仅剩的一个元素作为递回的结果回传,对回传的左子数列、右子数列依大小排列合并,将合并的结果作为递回的结果回传。

  • 合并

    将左子数列及右子数列依大小合并成一个新的数列(需执行 ⌈log 2n⌉ 回合):

    1. 若左子数列的数值都已填到新的数列,将右子数列中未填过的最小值填入新数列
    2. 若右子数列的数值都已填到新的数列,将左子数列中未填过的最小值填入新数列
    3. 将左子数列及右子数列中,未填过的最小值填到新的数列

给定一个阵列:

41, 24, 97,  6, 19, 53, 78

首先先将阵列拆成左子阵列与右子阵列

41, 24, 97,  6       19, 53, 78

持续递回将左子阵列与右子阵列继续切割,直到都只剩下一个数字

41, 24       97,  6       19, 53, 78

41, 24       97,  6       19, 53       78

41       24       97,  6       19, 53       78

41       24       97        6       19, 53       78

41       24       97        6       19       53       78

接下来将各阵列合并:最小值先填入

24, 41       97        6       19       53       78

24, 41        6, 97       19       53       78

24, 41        6, 97       19, 53       78

 6, 24, 41, 97       19, 53       78

 6, 24, 41, 97       19, 53, 78

 6, 19, 24, 41, 53, 78, 97

以上的范例大家可能有看没有懂,那就来看看动图和程序码吧:

合并排序法动图

public class MergeSort{
    public static void main(String[] args){
        int[] arr = {41, 24, 97, 6, 19, 53, 78};
        int n = arr.length;
        mergesort(arr, 0, n - 1);
        for(int i = 0 ; i < n ; i++){
            System.out.printf("%d ", arr[i]);
        }
    }

    public static void merge(int[] arr, int head, int mid, int tail){
        int lenA = mid - head + 1;
        int lenB = tail - (mid+1) + 1;
        int[] A = new int[lenA];    //左子数列
        int[] B = new int[lenB];    //右子数列
        int i,j;
        for(i = 0 ; i < lenA ; i++){
            A[i] = arr[head+i];
        }
        for(j = 0 ; j < lenB ; j++){
            B[j] = arr[mid+1+j];
        }
        i = 0;
        j = 0;
        int k = head;
        while(i < lenA && j < lenB){
            if(A[i] < B[j]){
                arr[k] = A[i];
                i++;
                k++;
            }
            else{
                arr[k] = B[j];
                j++;
                k++;
            }
        }
        //右子数列已经结束,将左子数列剩余的数复制到 arr
        while(i < lenA){
            arr[k] = A[i];
            i++;
            k++;
        }
        //左子数列已经结束,将右子数列剩余的数复制到 arr
        while(j < lenB){
            arr[k] = B[j];
            j++;
            k++;
        }
    }

    public static void mergesort(int[] arr, int head, int tail){
        if(head < tail){
            int mid = (head + tail) / 2;
            mergesort(arr, head, mid);
            mergesort(arr, mid+1, tail);
            merge(arr, head, mid, tail);    //合并
        }
    }
}

时间复杂度

  • 切割

    把阵列长度为 n 的切成 n 等分需要 n - 1 刀(步骤)。

  • 合并

    合并 n 个数字时,需要 n 个步骤,而每次切成一半,总共的回合术是需要以 2为底的 logn 次。

总共需要的步骤为 n - 1 + nlogn,所以时间复杂度为 O(nlogn)。

今天的排序法用到比较进阶的「递回」观念,因为演算法这部分在这个教学没有时间详细介绍,所以大家就尽量吸收。

有没有发现比前三天的还要快一点,那还能更快吗?答案是一般需要两两比较的排序法最快只能到这样了,要更快的话就需要用到其他更进阶的概念了。

对排序有兴趣的人,可以自行 google 快速排序法、堆积排序法、计数排序法等。

若你有照着程序码一行一行编写、理解,经过这四天相信你已经对阵列非常熟悉,也已经和 18 天前的你不一样了!


<<:  初学者跪着学JavaScript Day3 : 变数Variable、宣吿var

>>:  DAY3 - 开始写程序吧,建立 monorepo

30天零负担轻松学会制作APP介面及设计【DAY 23】

大家好,我是YIYI,今天要来分享我设计的APP的PHOTOTYPE制做过程。 今日进度 今天的进度...

【情蒐阶段】确认自己的目标、熟悉职缺市场

今天早上泡了杯 wushwush,打开我的 Leetcode, 啊又是一个觉得智力不足的 momen...

环境配置(Day2)

因为这次需要开发的内容涉及前後端,所以环境配置的部分会分成几个部分下来讨论 此次开发相关资料如下 g...

寻觅 webpack - 28 - 真实世界的 webpack - 配置多模式专案

本系列已集结成书从 0 到 Webpack:学习 Modern Web 专案的建置方式,这是一本完...

2022 年您应该准备的 6 大商业风险~综合风险管理 (IRM) 已被证明可以提供稳健且结构化的风险缓解方法。

一年的结束和新一年的开始提供了反思的时间;对於个人和组织。是时候重新集中精力,审查您的战略并制定来...