排序算法的稳定性
什么是稳定性
稳定性是指同样大小的样本再排序之后不会改变相对次序
- 对基础类型来说,稳定性毫无意义
- 对于非基础类型来说,稳定性有重要意义
- 有些排序算法可以实现为稳定的,而有些排序算法无论如何都无法实现称稳定的
算法面对相等时的态度,决定了它是不是稳定的
哪些排序算法是稳定的
选择排序:不稳定
关键步骤:每一轮选出一个最大值放在最后一位
反例:336333,6和3交换后,3本来在所有3的最后一个位置,被换到了第三个位置
冒泡排序:稳定
关键步骤:每一轮都是两两比较,只要设置相等就不比较,稳定性就不会被破坏
插入排序:稳定
关键步骤:当前数往前看,遇到相等就停下来
归并排序:稳定
关键步骤:merge的时候规定死遇到相等的先选左边就可以
快速排序:不稳定
关键步骤:partition过程无法保持稳定
反例:66663,最后一个3需要和第一个6交换,那么第一个6就跑到所有6的最后,所以不稳定
堆排序:不稳定
关键步骤:heapInsert 和 heapify 会遇到交换问题。
总结
- 不基于比较的排序,对样本数据有严格要求,不易改写(例如:基于桶排序思想的那些排序)
- 基于比较的排序,只要规定好两个样本怎么比大小就可以直接复用
- 基于比较的排序,时间复杂度的极限是 O(N*logN)
- 时间复杂度为 O(N*logN)、额外空间复杂度低于 O(N)、且稳定的基于比较的排序是不存在的。
- 为了绝对的速度选快排(快拍的常数时间是最少的)、为了省空间选堆排、为了稳定性选归并
常见的坑
- 归并排序的额外空间复杂度可以变成O(1),“归并排序 内部缓存法”,但是很难并且将变得不再稳定。(有病啊,为了追求O(1),都不在追求稳定性了,干嘛不用堆排序)
- “原地归并排序”是垃圾贴,会让时间复杂度变成O(N^2)(写一个插入排序不香吗)
- 快速排序稳定性改进,“01 stable sort”,但是会对样本数据要求更多。(用快排就是为了不给数据加限制,如果要加限制,为什么不用桶排序呢)
问题
在整型数组中,请把奇数放在数组左边,偶数放在数组右边,要求所有计数之间原始的相对次序不变,所有偶数之间原始相对次序不变。要求时间复杂度O(N),额外空间复杂度O(1)。
答:这其实就是一个个位01标准的partition。我们经典快排里面partition过程是做不到稳定性的。如果这么做还能做到稳定性,为什么快排不做成稳定的呢?
工程上对排序的改进
- 稳定性的考虑
- 充分利用 O(N*logN) 和 O(N^2) 排序各自的优势
问题1
Java的Array.sort是怎么实现的,首先会做个反射,看一看你传给我的这个东西啊,到底是值传递还是引用传递,如果是值传递的,想都不想就去给你做快排了;如果是引用传递的,就给你用归并排序了,这是为什么?
答:因为如果是值传递,就认为稳定性对你来讲是没用的,既然如此,就用最快的快排给你去弄。如果发现你是非基础类型,是按引用传递的类型,它不确定你在不在乎稳定性,所以就用归并排序。
问题2
在快排中,如果数目不够一个阈值,就直接用个插入排序就返回了,也不用快拍了,为什么?
答:快拍、归并、堆排 为什么优秀,是因为调度优秀,它的常数项其实是很大的。如果在小范围的数上,它们的常数操作是不低的。而插入排序,它常数项很低,但是调度不好。于是这种做法可以是的常数变得更优。