算法-8-归并排序

目录

1、描述

2、特点

3、自顶向下的归并排序

4、自底向上的归并排序

5、归并排序的优化

6、使用场景

7、归并排序是一种渐进最优的基于比较排序的算法。


1、描述

归并排序是基于递归方式进行排序的。它其实可以分为两步:

第一步 递归,(递归就是将数组递归拆分成很小的数组)

第二步,合并。(合并就是将这些很小的数组比较合并成有序的数组)

它的核心思想是一个长度为N的数组,按2的倍数,不断递归拆分成子数组,拆分到最后,每个数组只有两个元素的时候,将子数组a中两个元素比较,交换成有序的子数组,然后和有序的子数组(同等级,长度为2)b合并成c,子数组c这时候有4个元素,然后和他同等级的有序子数组d(长度=4)再合并,以此类推........最后合并成一个有序的完整的数组。

                                         

就那这个图说明,数组a长度是16,递归拆分后,每个数组中的元素都是两个(奇数数组会有剩一个的情况),然后将a[0..1]比较换成有序,然后和a[2..3]合并,再然后将a[0..3]和a[4..7]合并,最后再将a[0..7]和[8..15]合并,这样原数组就排序完成了。

这里忘说了一点,排序中的比较和交换位置,都是在合并的时候完成的。

2、特点

  • 时间复杂度为O( NlogN )
  • 归并排序最 吸引人的性质是它能够保证将任意长度为 N 的数组排序所需时间和 NlogN 成正比;
  • 主要缺点 则是它所需的额外空间和 N 成正比。----------------------------------------------------------------------缺点
  • 应用了高效算法设计当中的分治思想(拆分成小的子数组来排序)

3、自顶向下的归并排序

自顶向下的意思就是将数组从大数组不断拆分子数组,然后进行比较合并的。

public class Merge {
    private static double[] aux;

    public static void sort(double[] a) {
        aux = new double[a.length];
        sort(a, 0, a.length - 1);
    }
    /**
     * 递归拆分
     */
    public static void sort(double[] a, int lo, int hi) {
        if (lo >= hi)
            return;
        int mid = lo + (hi - lo) / 2;
        sort(a, lo, mid);
        sort(a, mid + 1, hi);
        if (a[mid + 1] < a[mid]) {
            merge(a, lo, mid, hi);
        }
    }
    /**
     * 将子数组合并
     */
    private static void merge(double[] a, int lo, int mid, int hi) {

        for (int k = lo; k <= hi; k++) {
            aux[k] = a[k];
        }
        int i = lo;
        int j = mid + 1;

        for (int k = lo; k <= hi; k++) {
            if      (i > mid)          a[k] = aux[j++];//如果i>mid了说明lo-mid之间的元素已经全部合并到a数组中了,剩下的就是mid+1-hi的元素依次放进去就行了
            else if (j > hi)           a[k] = aux[i++];//如果j>mid了,说明mid+1-hi之间的元素已经全部放入a数组中了
            else if (aux[i] < aux[j])  a[k] = aux[i++];//如果mid前面的数组元素比后面的数组元素小,优先把mid前面的元素放进去
            else                       a[k] = aux[j++];//如果mid后面的数组元素比前面的数组元素小,优先放mid后面的元素进入到a中
        }
    }
    private static void exch(double[] a, int i, int j) {
        double temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
    private static void show(double[] a) {
        System.out.println("\n");
        for (double item : a) {
            System.out.print((int)item + ",");
        }
    }
    public static void main(String[] args) {
        double[] a = { 55, 43, 23, 12, 13, 11, 7, 8, 88, 6, 4, 2, 3, 1, 9, 8, 7, 11, 56, 45, 22, 23,
                45, 66 };
        sort(a);
        show(a);
    }
}

上面的代码的拆分和合并的过程如下图:

                                                                       

4、自底向上的归并排序

自底向上的思想就是先将2个2个元素比较换成有序,然后再将4个4个的元素比较换成有序,然后是8个、16个、32个。

public class MergeBU {

    private static double[] aux;

    public static void sort(double[] a ){
        aux=new double[a.length];
        int N=a.length;
        
        for (int t=1;t<N;t=t+t){ //t的值为1,2,4,8,16......
           
            for (int k=0;k<N;k+=t+t){
                merge(a,k,k+t-1,Math.min(k+t+t-1,N-1));
            }
        }
    }
    private static void merge(double[] a, int lo, int mid, int hi) {
        for (int k = lo; k <= hi; k++) {
            aux[k] = a[k];
        }
        int i = lo;
        int j = mid + 1;
        for (int k = lo; k <= hi; k++) {
            if (i > mid)               a[k] = aux[j++];
            else if (j > hi)           a[k] = aux[i++];
            else if (aux[i] < aux[j])  a[k] = aux[i++];
            else                       a[k] = aux[j++];
        }
    }

    private static void exch(double[] a, int i, int j) {
        double temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
    private static void show(double[] a) {
        System.out.println("\n");
        for (double item : a) {
            System.out.print(item + ",");
        }
    }
    public static void main(String[] args) {
        double[] a = { 55, 43, 23, 12, 13, 11, 7, 8, 88, 6, 4, 2, 3, 1, 9, 8, 7, 11, 56, 45, 22, 23,
                45, 66 };
        sort(a);
        show(a);
    }
}

5、归并排序的优化

我们的优化从三方面入手:

1、如果递归拆分的数组长度小于7(建议取值5~15)的话,我们采用插入排序来加快排序的速度。下图绿色框

2、将拷贝数组aux[]作为参数穿进去。这样能节省每次merge时,拷贝数组的时间(空间消耗没减小)。下图红色框

3、当a[0..1]和a[2..3]合并的时候,如果a[1]比a[2]小,说明这两个数组的顺序是依次排的,这样就可以减少一次合并。下图蓝色框

                                        

60000条数据的话,优化后要比原始的归并排序快了0.8倍。

For 60000 random Doubles
  全部优化 is 0.8 times faster than 从顶向下归并排序
For 60000 random Doubles
  快速排序 is 0.7 times faster than 从顶向下归并排序
For 60000 random Doubles
  希尔排序 is 1.2 times faster than 从顶向下归并排序
For 60000 random Doubles
  插入排序 is 123.4 times faster than 从顶向下归并排序

6、使用场景

  1. 对于长度为 N 的任意数组,自顶向下的归并排序需要 1⁄2NlgN 至 NlgN 次比较。
  2. 对于长度为 N 的任意数组,自顶向下的归并排序最多需要访问数组 6NlgN 次

适用场景1:上面的1和2点说明了归并排序能够保证将任意长度为 N 的数组排序所需时间和 NlogN 成正比,这样让它拥有了可以处理数百万甚至更大的数组排序。但是缺点辅助数组aux导致的额外空间和N的大小成正比。

适用场景2:自底向上的归并排序比较适合用链表组织的数据。想象一下将链表先按大小为 1 的子链表进行 排序,然后是大小为 2 的子链表,然后是大小为 4 的子链表等。这种方法只需要重新组织链表链接 就能将链表原地排序(不需要创建任何新的链表结点)。

7、归并排序是一种渐进最优的基于比较排序的算法。

下面命题 I 说明没有任何排序算法能够用少于 ~NlgN 次比较将数组排序,这是其他排序算法复杂度的下限。归并排序在最坏情况下的比较次数和任意基于比较的其他排序算法所需的最少比较次数都是 ~NlgN。

                                         

                                         

 

 

 

 

 

 

 

 

 

 

 

 

 

发布了82 篇原创文章 · 获赞 16 · 访问量 26万+

猜你喜欢

转载自blog.csdn.net/qq_34589749/article/details/104034107