• 《算法》笔记 4


    • 归并方法
    • 自顶向下的归并排序
      • 实现
      • 性能分析
      • 优化:切换到插入排序
    • 自底向上的归并排序

    归并方法

    “归并”操作是将两个有序的数组合并成一个更大的有序数组,归并排序就是基于这一操作,先递归地将一个数组分成两半分别排序,然后将排序结果归并起来。
    归并的代码如下:

    private static void merge(Comparable[] a, int lo, int mid, int hi) { 
        int i = lo, j = mid + 1;
        for (int k = lo; k <= hi; k++) {
            aux[k] = a[k];
        }
    
        for (int k = lo; k <= hi; k++) {
            if (i > mid) {
                a[k] = aux[j++];
            } else if (j > hi) {
                a[k] = aux[i++];
            } else if (less(aux[i], aux[j])) {
                a[k] = aux[i++];
            } else {
                a[k] = aux[j++];
            }
        }
    }
    

    这种归并方法用了一个辅助数组aux[],在归并时,先将待归并的元素复制到辅助数组,然后将子数组a[lo..mid]和a[mid+1..hi]归并成一个有序的数组,同时将数组归并回a[]。i,j分别指向左右两个子数组的待归并元素,执行时如果左半边数组用尽,则取右半边元素,右指针加1,如果右半边数组用尽,则取左半边元素,左指针加1,如果两边数组都没用尽,则通过指针各取一个元素比较大小,将小的元素归并回原数组,同时对应的指针加1。
    这个归并方法能够将两个子数组排序,然后就可以通过它将整个数组排序。

    归并排序的代码实现有自顶向下和自底向上两种。

    自顶向下的归并排序

    实现

    public class Merge {
    	private static Comparable[] aux;
    
        private static void sort(Comparable[] a, int lo, int hi) {
            if (hi <= lo) {
                return;
            }
            int mid = lo + (hi - lo) / 2;
            sort(a, lo, mid);
            sort(a, mid + 1, hi);
            merge(a, lo, mid, hi);
        }
    
        public static void sort(Comparable[] a) {
            aux = new Comparable[a.length];
            sort(a, 0, a.length - 1);
        }
    }
    

    其中aux为归并所需的辅助数组,在排序开始前初始化。sort(Comparable[] a, int lo, int hi)方法递归地调用自己,将一个大的数组不断地分为左右两部分,并最终进行归并。将一个长度16的数组排序的轨迹如下图:

    要将a[0..15]排序, sortO方法会调用自己将a[0..7]排序,再在其中调用自己将a[0..3]和a[0..1]排序。在将a[0]和a[1]分别排序之后,终于才会开始将a[0]和a[1]归并。第二次归并是a[2]和a[3],然后是a[0..1]和a[2..3],以此类推。从这段轨迹可以看到, sort()方法的作用其实在于安排多次merge()方法调用的正确顺序。
    所以这种方式在不断地将数组分割然后排序,所以也称自顶向下的归并。

    性能分析

    关系归并排序算法的性能,还是从比较和访问数组的次数来分析。
    下面的树状图直观地表示了归并排序的执行过程,每个结点都表示一个sort()方法通过merge()方法归并而成的子数组。对于长度为N的数组,这颗二叉树的高度为lgN,在经过层层归并的过程中,每一层都最多需要N次比较,比如将a[0..7]和a[8..15]归并为a[0..15]时,16个元素都会经过一次比较被放回原数组,然后再往下层走,不管需要几次归并,处理的元素都是16个,既16次比较。每层N次比较,则一共NlgN次比较。

    所以归并排序的增长数量级为NlgN级别的,这相比之前的选择、插入排序的平方级别,要快太多了。

    将1万条随机整数进行排序时,插入排序与归并排序的速度对比如下:

    Insertion, 0.868 s
    Merge, 0.018 s
    

    优化:切换到插入排序

    上面的归并排序代码还可以继续优化,优化的方式之一便是在小规模数组时,切换到插入排序。因为当递归分割到最下面几层的子数组时,子数组的规模已经非常小了,继续递归只会使方法的调用过于频繁。用不同的方法处理小规模问题能改进大多数递归算法的性能,在小规模问题上,插入排序或者选择排序可能比归并排序更快,而且这样做可以有效减少递归的深度,减少方法调用的开销。

    public class MergeOptimize {
        private static int CUTOFF = 20;
    
        private static void sort(Comparable[] src, Comparable[] dst, int lo, int hi) {
            if (hi <= lo + CUTOFF) {
                insertionSort(dst, lo, hi);
                return;
            }
    
            int mid = lo + (hi - lo) / 2;
            sort(src, dst, lo, mid);
            sort(src, dst, mid + 1, hi);
            merge(src, dst, lo, mid, hi);
        }
        ...
    }
    

    在数组规模小于20的时候,改用插入排序。
    用10万条随机整数来检验优化后的效果:

    Merge, 0.305 s
    MergeOptimize, 0.199 s
    

    自底向上的归并排序

    自底向上的归并排序区别于自顶向上排序方法的地方在于,后者将一个大问题分割成小问题分别解决,然后用所有小问题的答案来解决整个大问题,属于分治思想的典型应用;而自底向上的归并排序则是直接从最小规模的数组开始归并,不断得到规模更大的数组,最终得以将整个数组排序。比如依次进行1-1,2-2,4-4归并,子数组的大小不断翻倍。

    public class MergeBU {
    
        private static Comparable[] aux;
        public static void sort(Comparable[] a) {
            int N = a.length;
            aux = new Comparable[N];
            for (int sz = 1; sz < N; sz = sz + sz) {
                for (int lo = 0; lo < N - sz; lo += sz + sz) {
                    merge(a, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1));
                }
            }
        }
        ...
    }
    

    自底向上的归并排序的增长数量级仍然是NlgN级别的,因为其原理仍然是基于归并的。在实际运行时,可能由于少了函数递归调用的开销,比自顶向下的归并排序还能更快,如下为10万条随机整数的测试结果:

    Merge, 0.279
    MergeBU, 0.172
    
  • 相关阅读:
    TensorFlow进阶(三)---变量的创建、初始化
    TensorFlow进阶(二)---张量的操作
    TensorFlow进阶(一)----张量的阶和数据类型
    TensorFlow入门
    卷积神经网络识别手写数字实例
    解决在win系统下使用DOS命令开启TensorBoard的问题及方法步骤
    TensorFlow------单层(全连接层)实现手写数字识别训练及测试实例
    TensorFlow------TFRecords的读取实例
    TensorFlow------TFRecords的分析与存储实例
    TensorFlow------读取二进制文件实例
  • 原文地址:https://www.cnblogs.com/zhixin9001/p/11461583.html
Copyright © 2020-2023  润新知