常见排序算法的最好、最坏、平均时间复杂度以及空间复杂度

思考

  • 为什么插入排序比冒泡排序更受欢迎?
  • 如何用快排思想在O(n)内查找第K大元素?
  • 如何根据年龄给100万用户数据排序?(线性排序)
  • 如何实现一个通用的、高性能的排序函数?(排序优化)

前言

我最近在系统整理一些 Java 后台方面的面试题和参考答案,有找工作需求的童鞋,欢迎关注我的 Github 仓库,如果觉得不错可以点个 star 关注 :


如何分析一个排序算法?


是否原地排序是否稳定最好、最坏、平均时间复杂度空间复杂度是否基于比较
冒泡排序O(n)、 O(n^2)、 O(n^2)O(1)
插入排序O(n)、 O(n^2)、 O(n^2)O(1)
选择排序O(n^2)、 O(n2)、O(n2)O(1)
希尔排序O(n)、O(n2)、O(n1.3)O(1)
快速排序O(nlogn)、O(n^2)、O(nlogn)O(logn)~O(n)
归并排序O(nlogn)、O(nlogn)、O(nlogn)O(n)
计数排序O(n+k)、O(n+k)、O(n+k),k 是数据范围O(n+k)
桶排序O(n)、O(n)、O(n)O(N+M),N表示待排数据个数,M表示桶个数
基数排序O(nk)、O(nk)、O(n*k),k 是维度O(n+k)
堆排序O(nlogn)、O(nlogn)、O(nlogn)O(1)

排序算法的执行效率

  • 1、最好情况、最坏情况、平均情况时间复杂度
  • 2、时间复杂度的系数、常数 、低阶
    • 时间复杂度反映数据规模 n 很大时的一个增长趋势,常忽略系数、常数、低阶
    • 实际软件开发中,数据规模可能比较小,比较同一阶时间复杂度的排序算法时,不能忽略系数、常数、低阶
  • 3、比较次数和交换(或移动)次数
    • 基于比较的算法会涉及两种操作:元素比较大小、元素交换或移动
    • 分析排序算法执行效率时,应该要考虑比较次数(或移动)次数

排序算法的内存消耗

  • 原地排序(Sorted in place):特指空间复杂度为 O(1) 的排序算法
  • 算法的内存消耗可以通过空间复杂度来衡量

排序算法的稳定性

  • 稳定的排序算法: 指待排序的序列中存在值相等的元素时,经过排序后,相等元素之间原有的先后顺序不变

如何选择合适的排序算法?

  • 对小规模数据进行排序,可以选择时间复杂度是 O(n^2) 的算法
  • 对大规模数据进行排序,时间复杂度是 O(nlogn) 的算法更加高效
  • 为了兼顾任意规模数据的排序,一般都会首选时间复杂度是 O(nlogn) 的排序算法来实现排序函数

如何优化快速排序?

  • 快速排序出现 O(n^2)时间复杂度的主要原因是因为分区节点选择不够合理
  • 最理想的分区节点是:被分区点分开的两个分区中,数据的数量差不多
  • 方法
    • 1、三数取中法(五数取中、十数取中等):从区间的首、尾、中间,分别取出一个数,然后对比大小,取这 3 个数的中间值作为分区点
    • 2、随机法:每次从要排序的区间中,随机选择一个元素作为分区点
    • 3、等等

解答思考题

  • 1. 为什么插入排序比冒泡排序更受欢迎?

    从代码实现上来看,冒泡排序的数据交换要比插入排序的数据移动要复杂,冒泡排序需要 3 个赋值操作,而插入排序只需要 1 个。

     // 冒泡排序中数据的交换操作:
        if (a[j] > a[j+1]) {  //  交换
            int tmp = a[j];
            a[j] = a[j+1];
            a[j+1] = tmp;
            flag = true;
        }
        
        //  插入排序中数据的移动操作:
        if (a[j] > value) {
            a[j+1] = a[j];  //  数据移动
        } else {
            break;
        }
    
  • 2. 如何用快排思想在O(n)内查找第K大元素?

    比如,4, 2, 5, 12, 3 这样一组数据,第 3 大元素就是 4。

    我们选择数组区间 A[0…n-1] 的最后一个元素 A[n-1] 作为 pivot,对数组 A[0…n-1] 原地分区,这样数组就分成了三部分,A[0…p-1]、A[p]、A[p+1…n-1]。

    如果 p+1=K,那 A[p] 就是要求解的元素;如果 K>p+1, 说明第 K 大元素出现在 A[p+1…n-1] 区间,我们再按照上面的思路递归地在 A[p+1…n-1] 这个区间内查找。同理,如果 K<p+1,那我们就在 A[0…p-1] 区间查找。
    在这里插入图片描述
    我们再来看,为什么上述解决思路的时间复杂度是 O(n)?

    第一次分区查找,我们需要对大小为 n 的数组执行分区操作,需要遍历 n 个元素。第二次分区查找,我们只需要对大小为 n/2 的数组执行分区操作,需要遍历 n/2 个元素。依次类推,分区遍历元素的个数分别为、n/2、n/4、n/8、n/16.……直到区间缩小为 1。

    如果我们把每次分区遍历的元素个数加起来,就是:n+n/2+n/4+n/8+…+1。这是一个等比数列求和,最后的和等于 2n-1。所以,上述解决思路的时间复杂度就为 O(n)。

  • 3. 如何根据年龄给100万用户数据排序?(线性排序)

    假设年龄的范围最小 1 岁,最大不超过 120 岁。可以遍历这 100 万用户,根据年龄将其划分到这 120 个桶里,然后依次顺序遍历这 120 个桶中的元素。这样就得到了按照年龄排序的 100 万用户数据。

  • 4. 如何实现一个通用的、高性能的排序函数?(排序优化)

    1.数据量不大时,可以采取用时间换空间的思路
    2.数据量大时,优化快排分区点的选择
    3.防止堆栈溢出,可以选择在堆上手动模拟调用栈解决
    4.在排序区间中,当元素个数小于某个常数是,可以考虑使用O(n^2)级别的插入排序
    5.用哨兵简化代码,每次排序都减少一次判断,尽可能把性能优化到极致

参考链接

  • 《数据结构与算法之美》专栏 王争
  • 19
    点赞
  • 101
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值