排序算法:归并排序

简介: 这篇文章本该发表于2018年4月份末,在 排序算法:快速排序 之后,但是不知道什么原因,这篇文章忘了在CSDN上发表,今天在看博客的时候突然发现,因此补上。

前言


这篇文章本该发表于20184月份末,在排序算法:快速排序之后,但是不知道什么原因,这篇文章忘了在CSDN上发表,今天在看博客的时候突然发现,因此补上。

 

概述


之前介绍过排序算法中效率很高的快速排序。本文将介绍另一个高效的排序算法——“归并排序

 

基本思想


归并排序的主要思想是分治法。主要过程是:

1.    n个元素从中间切开,分成两部分。(左边可能比右边多1个数)

2.    将步骤1分成的两部分,再分别进行递归分解。直到所有部分的元素个数都为1

3.    从最底层开始逐步合并两个排好序的数列。

 

思考


考虑一个问题,如何将两个有序数列合并成一个有序数列?

很简单,由于两个数列都已经有序,我们只需从两个数列的低位轮番拿出各自最小的数来PK就就行了,输的一方为小值,将这个值放入临时数列,然后输的一方继续拿出一个值来PK,直至有一方没有元素后,将另一方的所有元素依次接在临时数列后面即可。此时,临时数列为两个数列的有序合并。归并排序中的归并就是利用这种思想。对应的代码如下:


/**
 * 合并两个有序数列
 * array[first]~array[mid]为第一组
 * array[mid+1]~array[last]为第二组
 * temp[]为存放两组比较结果的临时数组
 */
private static void mergeArray(int array[], int first, int mid, int last, int temp[]) {
    int i = first, j = mid + 1; // i为第一组的起点, j为第二组的起点
    int m = mid, n = last; // m为第一组的终点, n为第二组的终点
    int k = 0; // k用于指向temp数组当前放到哪个位置
    while (i <= m && j <= n) { // 将两个有序序列循环比较, 填入数组temp
        if (array[i] <= array[j])
            temp[k++] = array[i++];
        else
            temp[k++] = array[j++];
    }
    while (i <= m) { // 如果比较完毕, 第一组还有数剩下, 则全部填入temp
        temp[k++] = array[i++];
    }
    while (j <= n) {// 如果比较完毕, 第二组还有数剩下, 则全部填入temp
        temp[k++] = array[j++];
    }
    for (i = 0; i < k; i++) {// 将排好序的数填回到array数组的对应位置
        array[first + i] = temp[i];
    }
}

 

例子


下面通过一个例子来看看归并排序是怎么工作的,原数组如下。

image.png

第一步:分解

首先将数组分解成两部分,即191537为一组,1225为一组,为了区分,我们起个名字叫第一层,如下图:

image.png

第二步:分解

继续分解,1915为一组,37为一组,12为一组,25为一组,这四组为第二层,如下图:

image.png

第三步:分解

继续分解,此时只剩下1915这一组可以分解,分解成1915,这两组为第三层,如下图:

image.png

第四步:归并

由于所有组都已经分解成只有1个元素,开始进行归并,从高层开始归并,即先归并第三层,比较第三层两组元素,19 < 15,因此将15排在19前面,由于已经没有元素,结束此次归并,如下图:

image.png

第五步:归并

继续归并,此次归并第二层,这一层有4个组,进行两两比较。首先,比较15193715 < 37,所以15放第一个位置,接着比较193719 < 37,所以19放第二个位置,此时第一组1519已经没有元素,于是将37填入1519之后。接着比较:122512 < 25,所以12放第一个位置,由于第一组12已经没有元素,于是将25填入12之后。归并的结果如下:

image.png

第六步:归并


继续归并,此次归并第一层,这一组有2个组,第一组:151937,第二组:1225。同样的,取两组的第1个数比较:15 > 12,所以12放第1个位置;接着取第二组的第2个数比较:15 < 25,所以15放第2个位置;接着取第一组的第2个数比较:19 < 25,所以19放第3个位置;接着取第一组的第3个数比较:37 > 25,所以25放第4个位置;由于第二组已经没有元素,所以37自然归入第5个位置。此时,归并结束,最终数组如下。

image.png

整个例子的完整过程图如下:

image.png

 

完整过程


用一张动图来展示整个归并排序的过程。图中,开始每个柱子的颜色都不同,代表所有数字被分解成了各自一组,红色的柱子代表归并后的数列。

image.png


整合


合并的代码在开头已经给过了,分解的代码较简单,就是递归调用归并方法,最后调用合并方法,最终代码如下。

public class MergeSort {
    public static void mergeSort(int[] array) {
        if (array == null || array.length == 0)
            return;
        int[] temp = new int[array.length];
        mergeSort(array, 0, array.length - 1, temp);
    }
    // 归并
    private static void mergeSort(int array[], int first, int last, int temp[]) {
        if (first < last) {
            int mid = (first + last) / 2;
            mergeSort(array, first, mid, temp); // 递归归并左边元素
            mergeSort(array, mid + 1, last, temp); // 递归归并右边元素
            mergeArray(array, first, mid, last, temp); // 再将二个有序数列合并
        }
    }
    /**
     * 合并两个有序数列
     * array[first]~array[mid]为第一组
     * array[mid+1]~array[last]为第二组
     * temp[]为存放两组比较结果的临时数组
     */
    private static void mergeArray(int array[], int first, int mid, int last, int temp[]) {
        int i = first, j = mid + 1; // i为第一组的起点, j为第二组的起点
        int m = mid, n = last; // m为第一组的终点, n为第二组的终点
        int k = 0; // k用于指向temp数组当前放到哪个位置
        while (i <= m && j <= n) { // 将两个有序序列循环比较, 填入数组temp
            if (array[i] <= array[j])
                temp[k++] = array[i++];
            else
                temp[k++] = array[j++];
        }
        while (i <= m) { // 如果比较完毕, 第一组还有数剩下, 则全部填入temp
            temp[k++] = array[i++];
        }
        while (j <= n) {// 如果比较完毕, 第二组还有数剩下, 则全部填入temp
            temp[k++] = array[j++];
        }
        for (i = 0; i < k; i++) {// 将排好序的数填回到array数组的对应位置
            array[first + i] = temp[i];
        }
    }
}

 

 

时间复杂度


归并排序的时间复杂度为O(nlogn),推导过程较复杂,在此不多赘述。

 

使用场景


从代码中可以看出,归并排序需要一个跟待排序数组同等空间的临时数组,因此,使用归并排序时需要考虑是否有空间上的限制。如果没有空间上的限制,归并排序是一个不错的选择。在本人的电脑测试,100万的随机数字,归并排序大约耗时150毫秒。

 

相关文章


排序算法:快速排序

排序算法:选择排序

排序算法:插入排序

排序算法:冒泡排序

相关文章
|
4月前
|
机器学习/深度学习 算法 搜索推荐
【初阶算法4】——归并排序的详解,及其归并排序的扩展
【初阶算法4】——归并排序的详解,及其归并排序的扩展
【初阶算法4】——归并排序的详解,及其归并排序的扩展
|
5月前
|
算法 前端开发 搜索推荐
前端算法之归并排序
前端算法之归并排序
31 0
|
2月前
|
算法 搜索推荐 Java
算法实战:手写归并排序,让复杂排序变简单!
归并排序是一种基于“分治法”的经典算法,通过递归分割和合并数组,实现O(n log n)的高效排序。本文将通过Java手写代码,详细讲解归并排序的原理及实现,帮助你快速掌握这一实用算法。
38 0
|
2月前
|
数据采集 搜索推荐 算法
【高手进阶】Java排序算法:从零到精通——揭秘冒泡、快速、归并排序的原理与实战应用,让你的代码效率飙升!
【8月更文挑战第21天】Java排序算法是编程基础的重要部分,在算法设计与分析及实际开发中不可或缺。本文介绍内部排序算法,包括简单的冒泡排序及其逐步优化至高效的快速排序和稳定的归并排序,并提供了每种算法的Java实现示例。此外,还探讨了排序算法在电子商务、搜索引擎和数据分析等领域的广泛应用,帮助读者更好地理解和应用这些算法。
25 0
|
3月前
|
存储 算法 搜索推荐
算法进阶之路:Python 归并排序深度剖析,让数据排序变得艺术起来!
【7月更文挑战第12天】归并排序是高效稳定的排序算法,采用分治策略。Python 实现包括递归地分割数组及合并已排序部分。示例代码展示了如何将 `[12, 11, 13, 5, 6]` 分割并归并成有序数组 `[5, 6, 11, 12, 13]`。虽然 $O(n log n)$ 时间复杂度优秀,但需额外空间,适合大规模数据排序。对于小规模数据,可考虑其他算法。**
67 4
|
3月前
|
算法 搜索推荐 C#
|
4月前
|
搜索推荐 算法 Java
Java中的快速排序、归并排序和堆排序是常见的排序算法。
【6月更文挑战第21天】Java中的快速排序、归并排序和堆排序是常见的排序算法。快速排序采用分治,以基准元素划分数组并递归排序;归并排序同样分治,先分割再合并有序子数组;堆排序通过构建堆来排序,保持堆性质并交换堆顶元素。每种算法各有优劣:快排平均高效,最坏O(n²);归并稳定O(n log n)但需额外空间;堆排序O(n log n)且原地排序,但不稳定。
38 3
|
4月前
|
算法
数据结构与算法-归并排序
数据结构与算法-归并排序
26 2
|
5月前
|
存储 搜索推荐 算法
归并排序算法深入解析
归并排序算法深入解析
|
4月前
|
搜索推荐 C语言
【C/排序算法】:快速排序和归并排序的非递归实现
【C/排序算法】:快速排序和归并排序的非递归实现
24 0
下一篇
无影云桌面