排序--归并排序

版权声明:本文源自简书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)

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到已排序好...
    依依玖玥阅读 1,177评论 0 2
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,101评论 0 52
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,706评论 0 15
  • 我想说的是一对年轻男女的事,稀松平常,只当故事,听听就罢。暂且叫他们宿与德森。 宿是普通不过的女子,算不上漂亮,也...
    iceaswater阅读 291评论 0 1
  • “江南忆,最忆是杭州。山寺月中寻桂子,郡亭枕上看潮头。何日更重游。”昨晚,G20杭州峰会文艺晚会《最忆是杭州》在西...
    正能量收获阅读 143评论 0 0