美文网首页
排序--归并排序

排序--归并排序

作者: tianma | 来源:发表于2016-04-27 00:26 被阅读268次

    版权声明:本文源自简书tianma,转载请务必注明出处:http://www.jianshu.com/p/df8a9e136295

    概念

    归并排序就是利用归并的思想实现的排序算法。归并排序的原理:假设初始序列含有n个记录,该序列可以看成n个有序的子序列,其中每个子序列的长度为1,然后两两归并,得到⌈n/2⌉(⌈x⌉表示不小于x的最小整数)个长度为2或者1的子序列,然后再两两归并,……,如此重复直到得到1个长度为n的有序序列为止。

    递归式归并

    演示
    比如我们待排序的数组是 {9, 1, 5, 8, 3, 7, 4, 6, 2},那么递归式的归并排序为流程为:

                     [9, 1, 5, 8, 3, 7, 4, 6, 2]
                        ↓                    ↓
              [9, 1, 5, 8, 3]             [7, 4, 6, 2]
               ↓            ↓              ↓         ↓ 
         [9, 1, 5]        [8, 3]      [7, 4]      [6, 2]
          ↓      ↓        ↓    ↓       ↓   ↓      ↓   ↓  
     [9, 1]     [5]      [8]  [3]     [7] [4]    [6]  [2]
      ↓  ↓       ↓        ↓    ↓       ↓   ↓      ↓   ↓  
    [9]  [1]    [5]      [8]  [3]     [7] [4]    [6]  [2] // 上面为拆分,下面为归并(合并)
        ↓        ↓        ↓    ↓       ↓   ↓      ↓   ↓  
     [1, 9]     [5]      [8]  [3]     [7] [4]    [6]  [2]
          ↓      ↓        ↓    ↓        ↓  ↓      ↓   ↓
         [1, 5, 9]       [3, 8]        [4, 7]     [2, 6]
               ↓           ↓              ↓         ↓
             [1, 3, 5, 9, 8]              [2, 4, 6, 7]
                       ↓                    ↓
                     [1, 2, 3, 4, 5, 6, 7, 8, 9]
    

    Java实现

    // 定义接口
    interface Sorter {
        /**
         * 将数组按升序排序
         */
        int[] sort(int[] arr);
    
        /**
         * 交换数组arr中的第i个位置和第j个位置的关键字
         */
        default void swap(int[] arr, int i, int j) {
            int tmp = arr[i];
            arr[i] = arr[j];
            arr[j] = tmp;
        }
    }
    
    // 递归式归并排序
    class MergeSorter implements Sorter {
    
        @Override
        public int[] sort(int[] arr) {
            int[] result = new int[arr.length];
            mergeSort(arr, result, 0, arr.length - 1);
            return result;
        }
    
        /**
         * 将乱序的src[start...end]归并排序为有序的des[start...end]
         * 
         * @param src
         *            归并前乱序数组
         * @param des
         *            归并后的有序数组
         * @param start
         *            归并的起始位置
         * @param end
         *            归并的终止位置
         */
        private void mergeSort(int[] src, int[] des, int start, int end) {
            if (start == end) {
                des[start] = src[start];
                return;
            }
            int[] tmp = new int[src.length];
            // 将src[start...end]分为src[start...mid]和src[mid+1...end]两部分
            int mid = (start + end) / 2;
            mergeSort(src, tmp, start, mid); // 递归,将src[start...mid]归并为有序的tmp[start...mid]
            mergeSort(src, tmp, mid + 1, end); // 递归,将src[mid+1...end]归并为有序的tmp[mid+1...end]
            // 将有序的tmp[start...mid]和tmp[mid+1...end]合并为des[start...end]
            merge(tmp, des, start, mid, end);
        }
    
        /**
         * 将有序的src[start, mid]和有序的src[mid+1, end]合并为有序的des[start,end];
         * src可能为乱序数组,但是src[start, mid]和src[mid+1, end]是有序的。
         * 
         * @param src
         *            乱序的原数组
         * @param des
         *            有序的目标数组
         * @param start
         *            数组第一部分起始位置
         * @param mid
         *            数组第一部分结束位置(两部分的分界点)
         * @param end
         *            数组第二部分结束位置
         */
        protected void merge(int[] src, int[] des, int start, int mid, int end) {
            int i; // src数组第一部分下标
            int j; // src数组第二部分下标
            int k; // des数组下标
            // 将较小的数依次移动到目标数组中
            for (i = start, k = start, j = mid + 1; i <= mid && j <= end;) {
                if (src[i] < src[j]) {
                    des[k] = src[i++];
                } else {
                    des[k] = src[j++];
                }
                k++;
            }
            // 将剩余的src[i...mid]复制到des数组中
            for (; i <= mid; i++) {
                des[k] = src[i];
                k++;
            }
    
            // 将剩余的src[j...end]复制到des数组中
            for (; j <= end; j++) {
                des[k] = src[j];
                k++;
            }
        }
    }
    

    复杂度
    时间复杂度:
    因为归并的递归操作其实就是二叉树的结构,故而,最好情况 = 最坏情况 = 平均情况 = O(nlogn)

    空间复杂度:
    因为递归式归并需要(1)与原始记录相同大小的空间来存放归并的结果以及(2)深度为logn的栈空间,所以空间复杂度为O(n+logn)

    非递归式归并

    演示
    又比如我们待排序的数组是 {9, 1, 5, 8, 3, 7, 4, 6, 2},那么非递归式的归并排序为流程为:

     [9] [1]    [5] [8]    [3] [7]   [4] [6]  [2]
       ↓  ↓      ↓   ↓      ↓    ↓     ↓   ↓   ↓
      [1, 9]    [5, 8]      [3, 7]    [4, 6]  [2]
        ↓          ↓          ↓       ↓        ↓ 
        [1, 5, 8, 9]       [3, 4, 6, 7]       [2]
                 ↓            ↓                ↓
            [1, 3, 4, 5, 6, 7, 8, 9]          [2]
                    ↓                         ↓
                    [1, 2, 3, 4, 5, 6, 7, 8, 9]
    

    Java实现

    // 非递归式归并排序
    class NonRecursiveMergeSorter extends MergeSorter {
    
        @Override
        public int[] sort(int[] arr) {
            mergeSort(arr);
            return arr;
        }
    
        private void mergeSort(int[] arr) {
            int len = arr.length;
            int result[] = new int[len];
            int k = 1;
            while (k < len) {
                mergePass(arr, result, k); // arr归并至result,此时间隔为k
                k = 2 * k; // 子序列长度加倍
                mergePass(result, arr, k); // result归并至arr,此时间隔翻倍
                k = 2 * k; // 子序列长度加倍
            }
        }
    
        /**
         * 将数组src中相邻长度为interval的子序列两两归并到des数组中
         * 
         * @param src
         *            源数组
         * @param des
         *            目标数组
         * @param interval
         *            两两合并的子序列长度
         */
        private void mergePass(int[] src, int[] des, int interval) {
            int i = 0;
            int len = src.length;
            while (i + 2 * interval - 1 < len) {
                // 两两合并
                merge(src, des, i, i + interval - 1, i + 2 * interval - 1);
                i = i + 2 * interval;
            }
            if (i + interval - 1 < len - 1) {
                // i+interval-1小于len-1,说明最后还剩余两个子序列,只不过最后的一个子序列长度不够interval
                // 那么将剩下的两个子序列进行合并
                merge(src, des, i, i + interval - 1, len - 1);
            } else {
                // 否则,最后只剩下单个子序列,则直接将该子序列加入到des尾部
                for (; i < len; i++) {
                    des[i] = src[i];
                }
            }
        }
    }
    

    复杂度
    时间复杂度:
    同递归式归并,最好情况 = 最坏情况 = 平均情况 = O(nlogn)

    空间复杂度:
    非递归式归并不需要保存方法栈信息,所以空间复杂度为O(n)

    所以非递归的递归算法性能要高于递归式归并算法。

    相关文章

      网友评论

          本文标题:排序--归并排序

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