5. 排序优化:
1. 如何选择合适的排序算法?
- 排序算法一览表
- 为什选择快速排序?
- 线性排序时间复杂度很低但使用场景特殊,如果要写一个通用排序函数,不能选择线性排序。
- 为了兼顾任意规模数据的排序,一般会首选时间复杂度为O(nlogn)的排序算法来实现排序函数。
- 同为O(nlogn)的快排和归并排序相比,归并排序不是原地排序算法,所以最优的选择是快排。
2. 如何优化快速排序?
-
导致快排时间复杂度降为O(n)的原因是分区点选择不合理,最理想的分区点是:被分区点分开的两个分区中,数据的数量差不多。如何优化分区点的选择?有2种常用方法,如下:
-
三数取中法
从区间的首、中、尾分别取一个数,然后比较大小,取中间值作为分区点。
如果要排序的数组比较大,那“三数取中”可能就不够用了,可能要“5数取中”或者“10数取中”。
-
随机法:
每次从要排序的区间中,随机选择一个元素作为分区点。
-
-
警惕快排的递归发生堆栈溢出,有2中解决方法,如下:
- 限制递归深度,一旦递归超过了设置的阈值就停止递归。
- 在堆上模拟实现一个函数调用栈,手动模拟递归压栈、出栈过程,这样就没有系统栈大小的限制。
-
通用排序函数实现技巧
-
对于小规模数据的排序,O(n2) 的排序算法并不一定比 O(nlogn) 排序算法执行的时间长。对于小数据量的排序,我们选择比较简单、不需要递归的插入排序算法
-
为什么说:O(n2) 时间复杂度的算法并不一定比 O(nlogn) 的算法执行时间长
时间复杂度代表的是一个增长趋势,如果画成增长曲线图,你会发现 O(n2) 比 O(nlogn) 要陡峭,也就是说增长趋势要更猛一些。但是,我们前面讲过,在大 O 复杂度表示法中,我们会省略低阶、系数和常数,也就是说,O(nlogn) 在没有省略低阶、系数、常数之前可能是 O(knlogn + c),而且 k 和 c 有可能还是一个比较大的数。 假设 k=1000,c=200,当我们对小规模数据(比如 n=100)排序时,n2的值实际上比 knlogn+c 还要小 knlogn+c = 1000 * 100 * log100 + 200 远大于10000 n^2 = 100*100 = 10000 所以,对于小规模数据的排序,O(n2) 的排序算法并不一定比 O(nlogn) 排序算法执行的时间长。对于小数据量的排序,我们选择比较简单、不需要递归的插入排序算法。
-
防止堆栈溢出,可以选择在堆上手动模拟调用栈解决
-
在排序区间中,当元素个数小于某个常数是,可以考虑使用O(n^2)级别的插入排序
-
.用哨兵简化代码,每次排序都减少一次判断,尽可能把性能优化到极致
-
3. 补充
java1.8中的排序,在元素小于47的时候用插入排序,大于47小于286用双轴快排,大于286用timsort归并排序,并在timesort中记录数据的连续的有序段的的位置,若有序段太多,也就是说数据近乎乱序,则用双轴快排,当然快排的递归调用的过程中,若排序的子数组数据数量小,用插入排序