排序算法之归并排序

来源:互联网 发布:上海公积金算法 编辑:程序博客网 时间:2024/06/07 11:00

归并排序

将两个的有序数列合并成一个有序数列,我们称之为”归并”。
归并排序(Merge Sort)就是利用归并思想对数列进行排序。

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

因归并排序需要一个辅助的和原数组同等大小的数组,所以是一种比较占内存的排序,但效率高且是一种稳定的排序算法。

归并排序工作原理

  1. 在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
  2. 设定三个指针,最初位置分别指向两个已经排序序列的起始位置和临时数组的起始位置
  3. 比较两个指针所指向的元素大小,选择相对小的元素放入到临时数组中,并移动指针(小元素数组指针和临时数组指针)到下一位置

重复步骤3直到某一指针超出序列尾,将另一序列剩下的所有元素直接复制到临时数组中,再将临时数组中的值拷贝到原数组对应位置

归并排序示意图

这里写图片描述

归并排序图文示例

比如上图中的最后一次合并,要将[2,5,7]和[4,8]两个已经有序的子序列,合并为最终序列[2,4,5,7,8],来看下实现步骤

  1. 设定[2,5,7]数组用a表示,[4,8]数组用b表示,临时数组用temp表示
  2. 设定三个指针i、j、k,最初位置分别指向数组a、b、temp的起始位置
  3. 比较a[i]和b[j]的大小
    1. 若a[i]≤b[j],则将a[i]复制到temp[k]中,并令i和k分别加1
    2. 若a[i]>b[j],则将b[j]复制到temp[k]中,并令j和k分别加1

第一次比较
i=0,j=0,a[i]=2,b[j]=4,2<4,把2赋值到temp数组中,并令i、k分别加1(指针向后移动1位),如图

这里写图片描述

第二次比较
i=1,j=0,a[i]=5,b[j]=4,5>4,把4赋值到temp数组中,并令j、k分别加1(指针向后移动1位),如图

这里写图片描述

第三次比较
i=1,j=1,a[i]=5,b[j]=8,5<8,把5赋值到temp数组中,并令i、k分别加1(指针向后移动1位),如图

这里写图片描述

第四次比较
i=2,j=1,a[i]=7,b[j]=8,7<8,把7赋值到temp数组中,并令i、k分别加1(指针向后移动1位),如图

这里写图片描述

第五次
注意:指针i加1后,超过a数组数,即代表a数组已经排完,所以后边可直接把b数组剩余元素赋值到temp数组中,如图

这里写图片描述

第六次
将temp数组中的元素全部拷贝到原数组中,排序完成

这里写图片描述

Java实现

public class MergeSort {    static int number = 1;    public static void main(String[] args) {        int[] arr = {7, 5, 2, 8, 4};        sort(arr);        System.out.println("排序后");        System.out.println(Arrays.toString(arr));    }    public static void sort(int[] arr) {        //在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间        int[] temp = new int[arr.length];        sort(arr, 0, arr.length - 1, temp);    }    private static void sort(int[] arr, int left, int right, int[] temp) {        if (left < right) {            int mid = (left + right) / 2;            sort(arr, left, mid, temp);//左边归并排序,使左子序列有序            sort(arr, mid + 1, right, temp);//右边归并排序,使右子序列有序            merge(arr, left, mid, right, temp);//将两个有序子数组合并        }    }    private static void merge(int[] arr, int left, int mid, int right, int[] temp) {        int i = left;//左序列指针        int j = mid + 1;//右序列指针        int k = 0;//临时数组指针        while (i <= mid && j <= right) {            if (arr[i] <= arr[j]) {                temp[k++] = arr[i++];            } else {                temp[k++] = arr[j++];            }        }        //将左边剩余元素填充进temp中        while (i <= mid) {            temp[k++] = arr[i++];        }        //将右序列剩余元素填充进temp中        while (j <= right) {            temp[k++] = arr[j++];        }        //将temp数组指针置为0,用于拷贝temp数组中的有序序列        k = 0;        //将temp中的元素全部拷贝到原数组中        while (left <= right) {            arr[left++] = temp[k++];        }        System.out.println("第" + (number++) + "趟排序");        System.out.println(Arrays.toString(arr));    }}

打印结果

第1趟排序[5, 7, 2, 8, 4]第2趟排序[2, 5, 7, 8, 4]第3趟排序[2, 5, 7, 4, 8]第4趟排序[2, 4, 5, 7, 8]排序后[2, 4, 5, 7, 8]

算法分析

  • 时间复杂度
    归并排序的时间复杂度是O(N*lgN)
    归并排序的形式就是一棵二叉树,它需要遍历的次数就是二叉树的深度,而完全二叉树的深度为(log2n),所以可以得出它的时间复杂度是O(N*lgN)

  • 空间复杂度
    因需要一个长度等于原数组长度的临时数组,故其辅助空间复杂度为O(n)

  • 稳定性
    归并排序是一种稳定的排序算法