美文网首页
归并排序

归并排序

作者: 囧囧有神2号 | 来源:发表于2017-08-15 11:03 被阅读0次
        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

    相关文章

      网友评论

          本文标题:归并排序

          本文链接:https://www.haomeiwen.com/subject/xurjrxtx.html