排序算法之——归并排序

归并排序

  • [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. 动图演示

相关推荐
艾莉丝努力练剑29 分钟前
【LeetCode&数据结构】单链表的应用——反转链表问题、链表的中间节点问题详解
c语言·开发语言·数据结构·学习·算法·leetcode·链表
_殊途2 小时前
《Java HashMap底层原理全解析(源码+性能+面试)》
java·数据结构·算法
珊瑚里的鱼5 小时前
LeetCode 692题解 | 前K个高频单词
开发语言·c++·算法·leetcode·职场和发展·学习方法
秋说6 小时前
【PTA数据结构 | C语言版】顺序队列的3个操作
c语言·数据结构·算法
lifallen7 小时前
Kafka 时间轮深度解析:如何O(1)处理定时任务
java·数据结构·分布式·后端·算法·kafka
liupenglove7 小时前
自动驾驶数据仓库:时间片合并算法。
大数据·数据仓库·算法·elasticsearch·自动驾驶
python_tty8 小时前
排序算法(二):插入排序
算法·排序算法
然我8 小时前
面试官:如何判断元素是否出现过?我:三种哈希方法任你选
前端·javascript·算法
risc1234568 小时前
BKD 树(Block KD-Tree)Lucene
java·数据结构·lucene
F_D_Z9 小时前
【EM算法】三硬币模型
算法·机器学习·概率论·em算法·极大似然估计