归并排序

2017-08-15  本文已影响0人  囧囧有神2号
    public static void sort(Comparable[] a) {
        Comparable[] aux = new Comparable[a.length];
        sort(a, aux, 0, a.length - 1);
        assert isSorted(a);
    }

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

    private static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) {

        assert isSorted(a, lo, mid);
        assert isSorted(a, mid + 1, hi);

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

        int i = lo, 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 (less(aux[j], aux[i]))
                a[k] = aux[j++];
            else
                a[k] = aux[i++];
        }
        assert isSorted(a, lo, hi);
    }

    private static boolean less(Comparable v, Comparable w) {
        return v.compareTo(w) < 0;
    }

    private static boolean isSorted(Comparable[] a) {
        return isSorted(a, 0, a.length - 1);
    }

    private static boolean isSorted(Comparable[] a, int lo, int hi) {
        for (int i = lo + 1; i <= hi; i++)
            if (less(a[i], a[i - 1]))
                return false;
        return true;
    }

上述时间复杂度为O(nlgn),空间复杂度为O(n),适用于排序大列表,基于分治法。
下列图片来展示长度16数组归并过程


归并结果轨迹 调用轨迹

归并排序有以下几点优化方法:

  1. 和快速排序一样,对于小数组可以使用插入排序或者选择排序,避免递归调用。
  2. 在merge()调用之前,可以判断一下a[mid]是否小于等于a[mid+1]。如果是的话那么就不用归并了,数组已经是有序的。原因很简单,既然两个子数组已经有序了,那么a[mid]是第一个子数组的最大值,a[mid+1]是第二个子数组的最小值。当a[mid]<=a[mid+1]时,数组整体有序。
  3. 为了节省将元素复制到辅助数组作用的时间,可以在递归调用的每个层次交换原始数组与辅助数组的角色。
  4. 在merge()方法中的归并过程需要判断i和j是否已经越界,即某半边已经用尽。可以用另一种方式,去掉检测是否某半边已经用尽的代码。具体步骤是将数组a[]的后半部分以降序的方式复制到aux[],然后从两端归并。对于数组{1,2,3}和{2,3,5},第一个子数组照常复制,第二个则从后往前复制,最终aux[]中的元素为{1,2,3,5,3,2}。这种方法的缺点是使得归并排序变为不稳定排序。代码实现如下:
    void merges(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) {
        for (int k = lo; k <= mid; k++) {
            aux[k] = a[k];
        }
        for(int k = mid+1; k <= hi; k++) {
            aux[k] = a[hi-j+mid+1];
        }
        int i = lo, j = hi;
        for (int k = lo; k <= hi; k++) {
            if (less(aux[j], aux[i])) {
                a[k] = aux[j--];
            }
            else {
                a[k] = aux[i++];
            }
        }
    }

下面代码将上述前三种优化结合在一起

    public void sort(Comparable[] a) {
        Comparable[] aux = a.clone();
        //将aux放在第一位
        sort(aux,a,0,a.length-1);
        assert isSorted(a);
    }

    private void sort(Comparable[] src, Comparable[] dst, int lo, int hi) {
        if (hi <= lo + CUTOFF) {
            insertionSort(dst, lo, hi);
            return;
        }
        int mid = (lo + hi)/2;
        //对调
        sort(dst, src, lo, mid);
        sort(dst, src, mid+1, hi);
        
//      if (less(src[mid], src[mid+1])) {
//          for (int i = lo; i <= hi; i++) dst[i] = src[i];
//          return;
//      }
        
        //更快
        if (less(src[mid], src[mid+1])) {
            System.arraycopy(src, lo, dst, lo, hi-lo+1);
            return;
        }
        //对调
        merge(src, dst, lo, mid, hi);
    }

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

    private void insertionSort(Comparable[] a, int lo, int hi) {
        for (int i = lo; i <= hi; i++) {
            for (int j = i; j>lo && less(a[j], a[j-1]); j--) {
                exch(a, j, j-1);
            }
        }
    }

还有一种非递归版本:

    public void sort(Comparable[] a) {
        int n = a.length;
        Comparable[] aux = new Comparable[n];
        for (int len = 1; len < n; len *= 2) {
            //自底向上的归并排序,先前一个与后一个排,再前两个与后两个,再前四个与后四个...
            //要考虑到末尾元素不够的情况
            for (int lo = 0; lo < n - len; lo += len * 2) {
                int hi = Math.min(lo + len * 2 - 1, n - 1);
                int mid = lo + len - 1;
                merge(a, aux, lo, mid, hi);
            }
        }
    }

    public void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) {
        for (int k = lo; k < hi; k++) {
            aux[k] = a[k];
        }

        int i = lo, 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 (less(aux[j], aux[i]))
                a[k] = aux[j++];
            else
                a[k] = aux[i++];
        }
    }
1.jpg
上一篇下一篇

猜你喜欢

热点阅读