排序算法:归并排序

简介: 这篇文章本该发表于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毫秒。

 

相关文章


排序算法:快速排序

排序算法:选择排序

排序算法:插入排序

排序算法:冒泡排序

相关文章
|
6天前
|
算法 前端开发 搜索推荐
前端算法之归并排序
前端算法之归并排序
10 0
|
6天前
|
搜索推荐 C语言
【C语言/数据结构】排序(归并排序|计数排序|排序算法复杂度)
【C语言/数据结构】排序(归并排序|计数排序|排序算法复杂度)
11 0
|
25天前
|
人工智能 算法 搜索推荐
归并排序——“数据结构与算法”
归并排序——“数据结构与算法”
|
1月前
|
搜索推荐 C语言 C++
【排序算法】C语言实现归并排序,包括递归和迭代两个版本
【排序算法】C语言实现归并排序,包括递归和迭代两个版本
|
1月前
|
存储 算法 搜索推荐
【数据结构与算法】归并排序(详解:递归与非递归的归并排序 | 赠:冒泡排序和选择排序)
【数据结构与算法】归并排序(详解:递归与非递归的归并排序 | 赠:冒泡排序和选择排序)
|
2月前
|
搜索推荐 算法
【数据结构】八大排序之归并排序算法
【数据结构】八大排序之归并排序算法
22 5
|
2月前
|
算法 索引
【数据结构与算法】:非递归实现快速排序、归并排序
上篇文章我们详细讲解了递归版本的快速排序,本篇我们来探究非递归实现快速排序和归并排序
【数据结构与算法】:非递归实现快速排序、归并排序
|
2月前
|
机器学习/深度学习 算法 搜索推荐
数据结构与算法(Java篇)笔记--归并排序
数据结构与算法(Java篇)笔记--归并排序
|
2月前
|
搜索推荐 算法 Python
python实现归并排序算法。
【2月更文挑战第9天】【2月更文挑战第24篇】python实现归并排序算法。
|
3月前
|
存储 搜索推荐 算法
【数据结构排序算法篇】----归并排序【实战演练】
【数据结构排序算法篇】----归并排序【实战演练】
28 0