排序二:归并、快排

文章结构

  1. 归并排序
  2. 快速排序
  3. 源码

1. 归并排序

1.1 什么是归并排序

归并排序的思想是:将待排序的区间平分成两个子区间,先对两个子区间进行排序,然后再将排好序的两个子区间合并成一个有序序列。而对子区间的排序也同样遵循这个思想

1.2 动画演示

归并排序.gif

1.3 代码实现

private static void sort(int[] a, int head, int tail) {
    /**
     * 递推公式:sort(a,head,tail)=merge(sort(a,head,m),sort(a,m+1,tail))
     * 退出条件:head=tail
     */
    if (head == tail) {
        return;
    }
    int mid = (head + tail) / 2;
    //对左右两个区间排序
    sort(a, head, mid);
    sort(a, mid + 1, tail);
    //将排序好的左右两个区间合并起来
    merge(a, head, tail, mid);
}

1.4 归并排序过程图解

归并排序过程分解图

1.5 性能分析

时间复杂度

使用递归树来分析归并排序的时间复杂度,递归树的最大深度为log2(n),每一层的比较次数为n,所以递归排序的时间复杂度在各种情况下都是O(nlogn)。


归并排序递归树

空间复杂度

递归排序过程中需要借助额外的空间来归并两个有序的序列,这个空间的最大长度等于带排序序列的长度,所以空间复杂度是O(n)

是否稳定

归并排序过程中不存在元素交换位置的情况,所以归并排序是稳定排序

2. 快速排序

2.1 什么是快速排序

快速排序的思想是:在要排序的区间选择任意的一个点作为分区点,遍历区间,将小于分区点的数据都排到分区点的左边;将大于分区点的数据都排到分区点的右边。这样经过一轮排序之后分区点的左边的数据都小于等于它,分区点右边的顺序都大于等于它,分区点的位置就确定了。然后再以相同的方式对左右两个区间进行排序,直到分区区间只含有一个元素位为止。

2.2 代码实现

private static void sort(int[] a, int left, int right) {
    /**
     * 递推公式:sort(a,p,r)=sort(a,p,m-1)+sort(a,m+1,r)
     * 结束条件:head>=tail
     */
    if (left < right) {
        int low = left;
        int hight = right;
        int pivot = a[low];
        //找到分区点的位置
        while (low < hight) {//有元素之间的位置交换,所以不是稳定排序
            while (low < hight && a[hight] >= pivot) {
                hight--;
            }
            a[low] = a[hight];
            while (low < hight && a[low] <= pivot) {
                low++;
            }
            a[hight] = a[low];
        }
        a[low] = pivot;
        //对左右两个分区进行排序
        if (left + 1 < low) {
            sort(a, left, low - 1);
        }
        if (right - 1 > low) {
            sort(a, low + 1, right);
        }
    }
}

2.3 过程分析

我们来分析一下一趟快速排序的过程

  1. 使用一个索引low指向待排序区间的最低位;使用一个索引hight指向待排序区间的最高位
  2. 选择待排序区间的最低位作为分区点pivot
  3. 从后往前遍历,移动hight索引,直到找到一个比分区点pivot小的值,将hight指向的值赋值给low指向的位置
  4. 然后从前往后遍历,移动low索引,直到找到一个比分区点pivot大的值,将low指向值赋值给hight指向的位置
  5. 重复3,4步骤,直到low=hight,此时low就是分区点在序列有序之后的位置
  6. 将pivot赋值给low,此时low前面的数据都比pivot小,low后面的数据都比pivot大,接着分别对low的前后两个区间执行这个排序过程

2.4 性能分析

时间复杂度

  1. 最好时间复杂度:O(nlogn),当每次分区点都刚好在待排序区间的正中间时
  2. 最坏时间复杂度:O(n^2),当每次分区都是1,n-1时,总共要n次分区
  3. 平均是否复杂度:O(nlogn),只有在极端情况下才会出现O(n^2),所以它的平均时间复杂度还是O(nlogn)

空间复杂度

这里只使用了几个零时空间来存储中间值,所以空间复杂度是O(1)

是否稳定

快排不是稳定排序,因为在上面的第3,4步中存在交换元素位置的情况

2.5 快速排序的应用

如何在O(n)的时间复杂度下从无序序列中找出第K大的数

使用快速排序查找第K大的数的思路:

一趟快排之后,分区点左边的数据都小于分区点,分区点右边的数据都大于分区点,也就是分区点的排序已经排好了。如果此时分区点正好是第k大的数,则直接返回这个数即可。如果分区点的位置倒数倒数第k个数之前,则在右边序列中继续查找;如果分区点的位置在倒数第k个数之后,则在前面的序列中查找。

代码实现

/**
 * 在O(n)的时间复杂度内找出无序数组中的第K大数
 *
 * @param array
 * @param k
 */
public static int getMaxK(int[] array, int k) {
    if (array == null || array.length < k) {
        return Integer.MIN_VALUE;
    }
    return sort(array, 0, array.length - 1, array.length - k);//计算第k大数在序列中的位置:array.length - k
}

private static int sort(int[] a, int left, int right, int index) {
    if (left < right) {
        int low = left;
        int hight = right;
        int pivot = a[low];
        //找到分区点的位置
        while (low < hight) {
            while (low < hight && a[hight] >= pivot) {
                hight--;
            }
            a[low] = a[hight];
            while (low < hight && a[low] <= pivot) {
                low++;
            }
            a[hight] = a[low];
        }
        a[low] = pivot;
        if (low == index) {//分区点的位置就是要查找的第K大数
            return pivot;
        } else if (low < index) {//分区点的位置在要查找的位置的左边
            return sort(a, low + 1, right, index);
        } else if (low > index) {//分区点的位置在要查找的位置的右边
            return sort(a, left, low - 1, index);
        }
    }
    return a[left];
}

测试用例

@Test
public void testGetMaxKth() {
    int size = 10;
    int[] array = new int[size];
    for (int i = 0; i < size; i++) {
        array[i] = new Random().nextInt(size * 10);
    }
    print(array, "org:");

    int index = 5;
    System.out.println("the " + index + "th:" + QuickSort.getMaxK(array, index));

    print(array, "sorted:");
}

private static void print(int[] array, String s) {
    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < array.length; i++) {
        builder.append(array[i] + ",");
    }
    System.out.println(s + builder.toString());
}

输出结果

org: 92,71,57,88,46,79,85,48,56,47,
the 5th: 71
sorted: 46,47,56,48,57,71,79,85,88,92,

我们看sorted之后的结果,71确实是无序序列的第5大数,它左边的都比它小,它右边的都比它大

时间复杂度分析

我们看一种情况,假设每次分区都在中间点上,然后我们直到最后一趟快排才找到第K大数,这个时候的时间复杂的是多少呢?我们总共经历了log2(n)趟快排,总共经历的比较次数是n+n/2+2/4+n/8……+n/n=2*n-1(等比数列求和),所以时间复杂度还是O(n)

3. 源码

源码和测试用例请查看github之排序

说明

文中图片来源:极客时间,王争《数据结构与算法之美》

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