排序算法之——归并排序

归并排序

  • [1. 基本思想](#1. 基本思想)
  • [2. 数据的分解](#2. 数据的分解)
  • [3. 数据的合并](#3. 数据的合并)
  • 4.归并排序的实现
    • [4.1 递归实现](#4.1 递归实现)
      • [4.1.1 一个易错点](#4.1.1 一个易错点)
      • [4.1.2 运行结果](#4.1.2 运行结果)
    • [4.2 非递归实现](#4.2 非递归实现)
      • [4.2.1 图示思路](#4.2.1 图示思路)
      • [4.2.2 代码实现](#4.2.2 代码实现)
      • [4.2.3 一个易错点](#4.2.3 一个易错点)
      • [4.2.4 修改后的代码](#4.2.4 修改后的代码)
      • [4.2.5 运行结果](#4.2.5 运行结果)
  • [6. 时间复杂度](#6. 时间复杂度)
  • [7. 空间复杂度](#7. 空间复杂度)
  • [8. 稳定性](#8. 稳定性)
  • [9. 动图演示](#9. 动图演示)

1. 基本思想

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治 (Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序 。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

2. 数据的分解

数据的分解采用递归分解,形式上和一棵完全二叉树相似。

3. 数据的合并

数据的合并过程不仅仅是将短数据合成长的,否则这仅仅是原来分解数据的逆过程。数据的合并过程中牵扯到对两组数据的排序再合并。为了实现这一目的,采用了双指针法。下面以图1.1中的

两组数据为例进行说明;

4.归并排序的实现

4.1 递归实现

c 复制代码
/**
     * 归并排序
     * 时间复杂度:O(n*logn)
     * 空间复杂度:O(n)
     * 稳定性:稳定
     * @param array
     */
    public static void mergeSort(int[] array){//为了使参数只有一个,保持统一性,调用了mergeFunc()方法
        mergeFunc(array,0, array.length-1);
    }

    public static void mergeFunc(int[] array,int left,int right){
        if(left >= right){
            return;
        }
        int mid = left + (right - left) / 2;
        mergeFunc(array,left,mid); //分解左边
        mergeFunc(array,mid+1,right);//分解右边
        //开始合并
        merge(array,left,mid,right);
    }

    public static void merge(int[] array,int left,int mid,int right){
        int s1 = left;
        int e1 = mid;
        int s2 = mid+1;
        int e2 = right;
        int[] tmpArray = new int[right - left + 1];
        int k = 0;
        //1.保证两个数组都有数据
        while(s1 <= e1 && s2 <= e2){
            if(array[s1] < array[s2]){
                tmpArray[k] = array[s1];
                k++;
                s1++;
            }else{
                tmpArray[k] = array[s2];
                k++;
                s2++;
            }
        }
        //2.其中一个数组已经拷贝完,将另一个数组剩下的部分拷贝回临时数组
        while(s1 <= e1){
            tmpArray[k] = array[s1];
            k++;
            s1++;
        }
        while(s2 <= e2){
            tmpArray[k] = array[s2];
            k++;
            s2++;
        }
        //3.将临时数组中的数据拷贝回原数组中
        for(int i = 0;i < k;i++){
            array[left + i] = tmpArray[i];//这一步array[]的下标要注意
        }
    }

4.1.1 一个易错点

在将临时数组tmpArray[]中的数据放回原数组array[]的过程中,要特别注意下标问题。如图:

黄线框里的代码不能写成array[i] = tmpArray[i]。以一张图说明:

4.1.2 运行结果

4.2 非递归实现

4.2.1 图示思路

4.2.2 代码实现

c 复制代码
/**
 * 并排序(非递归实现)
 * @param array
 */
public static void mergeSortNotRecursion(int[] array){//为了使参数只有一个,保持统一性,调用了mergeFunc()方法
        int gapNumber = 1; //gapNumber表示每组数据的个数
        //最外层控制组数
        while(gapNumber < array.length){
            for(int i = 0;i < array.length;i += gapNumber){//i每次走gapNumber个距离
                int left = i;
                int mid = left + gapNumber - 1;
                int right = mid + gapNumber;
                merge(array,left,mid,right);
            }
            gapNumber *= 2;
        }
    }

 public static void merge(int[] array,int left,int mid,int right){
        int s1 = left;
        int e1 = mid;
        int s2 = mid+1;
        int e2 = right;
        int[] tmpArray = new int[right - left + 1];
        int k = 0;
        //1.保证两个数组都有数据
        while(s1 <= e1 && s2 <= e2){
            if(array[s1] < array[s2]){
                tmpArray[k] = array[s1];
                k++;
                s1++;
            }else{
                tmpArray[k] = array[s2];
                k++;
                s2++;
            }
        }
        //2.其中一个数组已经拷贝完,将另一个数组剩下的部分拷贝回临时数组
        while(s1 <= e1){
            tmpArray[k] = array[s1];
            k++;
            s1++;
        }
        while(s2 <= e2){
            tmpArray[k] = array[s2];
            k++;
            s2++;
        }
        //3.将临时数组中的数据拷贝回原数组中
        for(int i = 0;i < k;i++){
            array[left + i] = tmpArray[i];//这一步array[]的下标要注意
        }
    }

4.2.3 一个易错点

4.2.4 修改后的代码

c 复制代码
while(gapNumber < array.length){
            for(int i = 0;i < array.length;i += gapNumber){//i每次走gapNumber个距离
                int left = i;
                int mid = left + gapNumber - 1;
                if(mid >= array.length){
                    mid = array.length - 1;
                }
                int right = mid + gapNumber;
                if(right >= array.length){
                    right = array.length - 1;
                }
                merge(array,left,mid,right);
            }
            gapNumber *= 2;
        }

4.2.5 运行结果

6. 时间复杂度

整个归并排序的过程相当于一棵完全二叉树的创建过程。假设待排序的数据的容量为n,则完全二叉树的高度为log2(n+1)(向上取整),每一层遍历n个数据,最终用时nlog2(n+1),故最终的时间复杂度为O(nlog2(n))。

7. 空间复杂度

由于每次归并两组数据的过程中都借用了临时数组tmpArray[],且tmpArray[]的长度至少要等于两组数据的元素个数之和,故最终的空间复杂度为O(n)。

8. 稳定性

归并排序是一种稳定排序。

9. 动图演示

相关推荐
迷迭所归处4 分钟前
动态规划 —— 子数组系列-单词拆分
算法·动态规划
爱吃烤鸡翅的酸菜鱼4 分钟前
Java算法OJ(8)随机选择算法
java·数据结构·算法·排序算法
寻找码源1 小时前
【头歌实训:利用kmp算法求子串在主串中不重叠出现的次数】
c语言·数据结构·算法·字符串·kmp
Matlab精灵1 小时前
Matlab科研绘图:自定义内置多款配色函数
算法·matlab
诚丞成1 小时前
滑动窗口篇——如行云流水般的高效解法与智能之道(1)
算法
手握风云-1 小时前
数据结构(Java版)第二期:包装类和泛型
java·开发语言·数据结构
带多刺的玫瑰2 小时前
Leecode刷题C语言之统计不是特殊数字的数字数量
java·c语言·算法
爱敲代码的憨仔2 小时前
《线性代数的本质》
线性代数·算法·决策树
熬夜学编程的小王3 小时前
【C++篇】深度解析 C++ List 容器:底层设计与实现揭秘
开发语言·数据结构·c++·stl·list
yigan_Eins3 小时前
【数论】莫比乌斯函数及其反演
c++·经验分享·算法