归并排序 O(nLogn)

归并排序

归并排序的思想是分治法+回溯,将一个无序的数组先按照原来的一半进行拆分,一直拆分到最后一个元素,然后开始回溯,排序开始的过程是再回溯时开始排序的。

算法.png

思想总结:

  1. 将源数组进行拆分,每次拆分一半,由图可以分析出,当arr.length=n,需要拆分log2^8=3次
  2. 当拆分到不能再拆分,也就是分组到每个组只有1个元素,停止拆分
  3. 开始排序并回溯排序,每次排序的时间复杂度为O(n)
  4. 总的时间复杂度为n x log2^n,时间复杂度不考虑系数和底数,所以n x log2^n等价于 O(nlogn)

==其实归并总结就是2部分 1. 先拆分 2 回溯排序==

代码分析
从分析我们知道,想要实现回溯,那么通常是使用递归的。那么回溯的问题解决了,我们要如何实现O(n)的时间复杂度呢?。以下图i=2对应回溯为例子:

1592837889(1).jpg

如果我们分别对数组arr1={2,3,6,8},和arr2={1,4,5,7},如果使用选择,插入,冒泡等排序都是O(n^2), 显然最后算法变成n^2*logn。我们可以换个思路:

算法.png

说下回溯的过程:

  1. 当我们回溯排序时,源数组不变,我们将拆分后的数组历史保存到另外的数组中,如临时数组arr1={2,3,6,8},临时数组arr2={1,4,5,7},临时数组总空间O(n),(源arr的顺序是上次回溯后排序得到的)。
  2. 然后我们定义几个指针:

left->arr1 起始位置
mid->arr1结束的位置
right->arr2->结束的位置
i指向左边待排序的元素
j指向右边待排序的位置
k指向源数组中排好序的元素的下一个位置(指向新排序元素将要放置的位置)

  1. left,mid,right作为判断结束的条件,i,j不断移动指向左右数组要排序的元素,k用来作为存放元素的位置
    //对数组[l...r] 全闭空间排序
    public static void mergeSort(int[] arr) {

        mergeSort(arr, 0, arr.length - 1);
    }

    public static void mergeSort(int[] arr, int l, int r) {
        if (l == r) {//只有一个元素了,那么它就是有序的
            return;
        } else {
            //找到中间边界mid 拆分2个数组[l...mid]和[mid+1...r]
            int mid = (r - l) / 2 + l;
            //左边继续拆分
            mergeSort(arr, l, mid);
            //右边边继续拆分
            mergeSort(arr, mid + 1, r);

            //一直拆分到l==r 说明只有一个元素 retuen
            //然后开始回溯合并排序
            merge(arr, l, mid, r);
        }
    }

    public static void merge(int[] arr, int l, int mid, int r) {
        //1. l不一定是从0开始的 2.因为是 对数组[l...r] 全闭空间排序 所以要+1
        int[] temp = new int[r - l + 1];

        //赋值临时数组 也就是拆分左右数组,将arr拆分成arr1和arr2
        for (int i = l; i <= r; i++) {
            //上面说过l不是从0开始的  但是我们的temp是0开始的,所以要进行l的偏移
            temp[i - l] = arr[i];
        }

        int i = l, j = mid + 1;
        for (int k = l; k <= r; k++) {
            //当arr2排序完成 但是arr1还有元素 那么直接赋值
            if (j > r && i <= mid) {
                arr[k] = temp[i - l];
                i++;
                 //当arr1排序完成 但是arr2还有元素 那么直接赋值
            } else if (i > mid && j <= r) {
                arr[k] = temp[j - l];
                j++;
            } else if (temp[i - l] < temp[j - l]) {
                arr[k] = temp[i - l];
                i++;
            } else {
                arr[k] = temp[j - l];
                j++;
            }
        }
    }

首先根据代码理解思想。要是还是有点似懂非懂的话。下面这张图应该能帮到你。下面描述的情况是2-2归并。最后左面4个元素有序,右边4个元素有序(对应上面分析图的上一步)

算法.png

归并排序--迭代法

归并排序,还有一种迭代法。递归法归并排序使用的是先自上而下拆分(分治),再自底向上归并,那么如果我们直接通过递归,将数组按照size=1,2,4,8...n 去拆分,那么合并的数组为arr1-arr2: 1-1.2-2,4-4,8-8...左右两边分别代表arr1和arr2的长度。代码:

/**
     * 自定向上排序
     *
     * @param arr 待排序数组
     * @param n   数组的长度
     */
    public static void mergeSort(int[] arr, int n) {
        /*
         * 数组分2层循环 第一层是确定分组后每个组的长度,按照上面图的图示所知
         * size分别为1,2,4,8...
         * 当size=1 那么arr1.length=1 arr2.length=1 所以 1-1 排序 最终 “每” 2个元素有序
         * 当size=2 那么arr1.length=2 arr2.length=2 所以 2-2 排序 最终 “每” 4个元素有序
         * 当size=4 那么arr1.length=4 arr2.length=4 所以 4-4 排序 最终 “每” 8个元素有序
         * 所以这层循环 就是为了帮助我们创建符合要求变化的size大小
         * 最开始数组长度=1 也就是1个元素 他就是有序的 那么size = 2 * size这个算式就帮助我们迭代创建size分别为1,2,4,8...
         *
         */
        for (int size = 1; size <= n; size = 2 * size) {
            /*
             *i表示的是每个要合并分作的起始坐标也就是left 我们知道 left-right是通过mid分成arr1和arr2的
             * 也就是[l...mid]-[mid+1...r] 等价于[l...size-1]和[size...r]
             * 所以i的取值变化为i = i + 2 * size
             * 同时i不能越界 所以i<n
             */
            for (int i = 0; i + size < n; i = i + 2 * size) {
                /*
                 * 上面分析[l...mid]-[mid+1...r] 等价于[l...size-1]和[size...r]
                 * 所以 i等价l i + size - 1等价mid i + size + size - 1等价r
                 * 虽然i<n合法 但是i + size可能越界,
                 * 同时当i+size>=n说明 只有arr1 arr2为null,那么也就不归并了 他就是有序的
                 * 因为从上面得知,归并的前提是arr1和arr2是有序的
                 * 
                 * 
                 * i + size + size - 1相当于r 他可能越界 所以Math.min(i + size + size - 1, n - 1)
                 * 
                 */
                merge(arr, i, i + size - 1, Math.min(i + size + size - 1, n - 1));
            }
        }
    }

归并排序优化

现在直接上代码

 public static void mergeSort(int[] arr, int l, int r) {
        if (r-l<=15) {
           insertSort(arr,l,r);// 1 .
            return;
        } else {
            //找到中间边界mid 拆分2个数组[l...mid]和[mid+1...r]
            int mid = (r - l) / 2 + l;
            //左边继续拆分
            mergeSort(arr, l, mid);
            //右边边继续拆分
            mergeSort(arr, mid + 1, r);

            //一直拆分到l==r 说明只有一个元素 retuen
            //然后开始回溯合并排序
            if (arr[mid] > arr[mid + 1])//2.
                merge(arr, l, mid, r);
        }
    }
  1. 当拆分到足够小的时候选择使用插入排序,原始是插入排序对相对有序的数组效率比较高,所以当数组越小的时候有序的几率就越大,所以使用插入排序
  2. (arr[mid] <= arr[mid + 1]) 也就是所arr1中的最大的元素已经比arr2最小的元素还要小,那么arr1所有元素就小于等于arr2的所有元素,因为再归并中arr1和arr2都是有序的,那么此时[l...r]就是有序的,所以当(arr[mid] > arr[mid + 1])时我们才需要排序

思考

逆序对 ?

什么是逆序对.逆序对是判断一个数组有序程度的一个标示。一个完全有序的数组,逆序对个数=0
 1 2 3 4 5 6 8 7 一个逆序对   
 利用归并思想,当向上归并:
 
 2 3 6 8 | 1 4 5 7 
 
 2与1比较 1<2  那么1比左边2 3 6 8 都要小那么此时逆序对4 
 继续归并到
 1 2 3 当4<6时 4比左边6 8 小 逆序对未2  
 
 最终将计数相加

感谢:

https://juejin.im/post/5a96d6b15188255efc5f8bbd
https://juejin.im/post/5ab4c7566fb9a028cb2d9126
https://blog.csdn.net/dugudaibo/article/details/79508198
算法与数据结构-综合提升 C++版

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