教你如何选择排序算法并避开面试中的算法坑题

简介: 教你如何选择排序算法并避开面试中的算法坑题

稳定性

同样值的个体之间,如果不因为排序而改变相对次序,就是这个排序是有稳定性的;否则就没有。

比如[1,4,5,4]排序之后就是[1,4,4,5],对于两个4,如果还是保持之前的顺序,那就是有稳定性。

什么时候需要稳定性?

假设现在有一堆学生的数据,每个学生有两个字段:class班级和age年龄。首先我们按年龄进行排序,排序之后再按班级排序,第二次排序是稳定的,那么每个班级里的学生都是按年龄排序的。稳定性就是保持相同值之间的相对次序。在实际的一些处理场景中还是很有用的。

排序算法总结

不具备稳定性的排序

选择排序、快速排序、堆排序

具备稳定性的排序

冒泡排序、插入排序、归并排序、一切桶排序思想下的排序

排序算法总结

算法 时间复杂度 空间复杂度 稳定性
选择排序 O(N2)O(N^2)O(N2) O(1)O(1)O(1)
冒泡排序 O(N2)O(N^2)O(N2) O(1)O(1)O(1) 可以实现稳定性(关键看数值相等时是否要交换)
插入排序 O(N2)O(N^2)O(N2) O(1)O(1)O(1) 可以实现稳定性(关键看数值相等时是否要交换)
归并排序 O(NlogN)O(NlogN)O(NlogN) O(N)O(N)O(N) 可以(关键是merge时如何处理相等的情况)
快速排序 O(NlogN)O(NlogN)O(NlogN) O(logN)O(logN)O(logN)
堆排序 O(NlogN)O(NlogN)O(NlogN) O(1)O(1)O(1)

如何选择排序算法

从上述表格中可以选出三种最优的,下面进行分析优缺点:

  • 归并排序:有稳定性;空间复杂度较高
  • 快速排序:常数项指标最低,意味最快;但没有稳定性,空间复杂度无法做到O(1)
  • 堆排序:空间使用最少

基于比较的排序,能否做到时间复杂度少于O(N*logN)?

目前不行

在时间复杂度为O(N*logN)的前提下,是否能做到空间复杂度少于O(N),并具备稳定性?

目前不行

结论:如果要选择一种排序的话,一般来说,选择快速排序(常数项指标比归并排序最低),如果有空间限制,就选择堆排序,需要稳定性用归并排序。        目前没有找到时间复杂度O(N*logN),额外空间复杂度为O(1),又稳定的排序。

常见的坑

  • 归并排序的额外空间复杂度可以变成O(1),但是非常难,不需要掌握,有兴趣可以搜“归并排序 内部缓存法”(这种做法会失去稳定性,并且代码较难实现,不如直接使用堆排序)。
  • “原地归并排序”的帖子都是垃圾,虽然会让归并排序的空间复杂度降为O(1),但同时会让时间复杂度变成 O(N^2),不如直接用插入排序
  • 快速排序可以做到稳定性问题,但是非常难,不需要掌握,可以搜“01 stable sort”。(会将空间复杂度提高为O(N),不如直接用归并)
  • 所有的改进都不重要,因为目前没有找到时间复杂度O(N*logN),额外空间复杂度为O(1),又稳定的排序。
  • 有一道题目,是奇数放在数组左边,偶数放在数组右边,还要求原始的相对次序不变,碰到这个问题,可以怼面试官。(经典的快排做不到稳定性,快排的0和1标准相当于奇偶标准)

工程上对排序的改进

  • 充分利用O(N*logN)和O(N^2)排序各自的优势
  • 稳定性的考虑

充分利用O(N*logN)和O(N^2)排序各自的优势

实际使用中,我们一般不会只使用一种算法,而是使用综合算法,比如快排+插入,也可以归并+插入。

快排+插入

比如说,经典的快排算法是这样的:

public static void quickSort(int[] arr, int l,int r){ 
    if(l==r) return;
    swap(arr, l+(int)(Math.random() * (R-L+1)), r); 
    int[] p = partition(arr,l,r); 
    quickSort(arr,l,p[0]-1); // 小于区 
    quickSort(arr,p[1]+1,r); // 大于区 
}
复制代码

如何改进?

当样本量大的时候,利用快排的调度思想,左右两部分进行递归,所以总体的调度方式是快排;但在小样本量下,对这小样本部分进行插入排序。也就是说快排+插入结合的方式。因为快排时间复杂度O(N*logN),插入时间复杂度是O(N^2),在小样本情况下,插入排序的瓶颈不明显,并且其常数时间很低,两个算法结合,就是结合了小样本上插入排序的常数时间低的优势与大样本上快速排序的调度优势。

当样本量比较小的时候,在小样本量上直接做插入排序。我们可以改成这样:

public static void quickSort(int[] arr, int l,int r){ 
   if(l==r) return;
   if(l>r-60){
       // 在arr[l...r]插入排序
       // O(N^2)小样本量的时候,跑得快
       return;
   }
   swap(arr, l+(int)(Math.random() * (R-L+1)), r); 
   int[] p = partition(arr,l,r); 
   quickSort(arr,l,p[0]-1); // 小于区 
   quickSort(arr,p[1]+1,r); // 大于区 
}
复制代码

系统中的排序算法

很多系统自带了排序算法,内部是怎么做的?

一般来说,如果是基础类型,使用的是快排,如果是非基础类型(自定义类型),就用的归并排序。

这种做法是基于稳定性的考量。如果是基础类型,会认为稳定性是没有用的,就默认使用常数时间比较低的快速排序,如果是非基础类型的数据,系统无法判定你是否需要稳定性,就使用归并排序来保证稳定性。

在C/C++/Java的底层,底层库的排序,代码非常多,在极致优化排序算法,但归根结底就是利用各个排序的优势,进行拼装结合。

对于算法的优化,都是从稳定性复杂度这两个维度进行考量的。



相关文章
|
6天前
|
负载均衡 NoSQL 算法
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
这篇文章是关于Java面试中Redis相关问题的笔记,包括Redis事务实现、集群方案、主从复制原理、CAP和BASE理论以及负载均衡算法和类型。
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
|
12天前
|
算法 Go
[go 面试] 雪花算法与分布式ID生成
[go 面试] 雪花算法与分布式ID生成
|
4天前
|
算法
聊聊一个面试中经常出现的算法题:组合运算及其实际应用例子
聊聊一个面试中经常出现的算法题:组合运算及其实际应用例子
|
6天前
|
搜索推荐 算法 Java
经典排序算法之-----选择排序(Java实现)
这篇文章通过Java代码示例详细解释了选择排序算法的实现过程,包括算法的基本思想、核心代码、辅助函数以及测试结果,展示了如何通过选择排序对数组进行升序排列。
经典排序算法之-----选择排序(Java实现)
|
13天前
|
机器学习/深度学习 算法 数据中心
【机器学习】面试问答:PCA算法介绍?PCA算法过程?PCA为什么要中心化处理?PCA为什么要做正交变化?PCA与线性判别分析LDA降维的区别?
本文介绍了主成分分析(PCA)算法,包括PCA的基本概念、算法过程、中心化处理的必要性、正交变换的目的,以及PCA与线性判别分析(LDA)在降维上的区别。
29 4
|
12天前
|
算法
突击面试:解密面试官的算法题集合
突击面试:解密面试官的算法题集合
|
14天前
|
机器学习/深度学习 算法
【机器学习】解释对偶的概念及SVM中的对偶算法?(面试回答)
解释了对偶的概念,指出对偶性在优化问题中的重要性,尤其是在强对偶性成立时可以提供主问题的最优下界,并且详细阐述了支持向量机(SVM)中对偶算法的应用,包括如何将原始的最大间隔优化问题转换为对偶问题来求解。
26 2
|
14天前
|
机器学习/深度学习 算法 数据挖掘
|
16天前
|
算法
分享几道大厂面试算法题
分享几道大厂面试算法题
|
1月前
|
算法 Java
Java面试题:解释垃圾回收中的标记-清除、复制、标记-压缩算法的工作原理
Java面试题:解释垃圾回收中的标记-清除、复制、标记-压缩算法的工作原理
33 1