14.排序优化

14.排序优化:如何实现一个通用的、高性能的排序函数?

markdown文件已上传至github

1.如何选择合适的排序算法

14.排序优化

线性排序算法的时间复杂度比较低,使用场景比较特殊,所以实现通用的排序算法不能选择线性排序算法。

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

时间复杂度为O(nlogn)O(nlogn):归并排序、快速排序、堆排序。后面两种用的多一点。比如 Java 语言采用堆排序实现排序函数,C 语言使用快速排序实现排序函数。归并排序空间复杂度为O(n)O(n),所以用的比较少。

快速排序在最坏的情况下时间复杂度是O(n2)O(n^2),如何解决这个“复杂度恶化”的问题?

2.如何优化快速排序?

为什么最坏情况下快速排序的时间复杂度是 $O(n^2) 退呢?我们前面讲过,如果数据原来就是有序的或者接近有序的,每次分区点都选择最后一个数据,那快速排序算法就会变得非常糟糕,时间复杂度就会退化为O(n^2)$ 。实际上,这种O(n2)O(n^2) 时间复杂度出现的主要原因还是因为我们分区点选得不够合理。

理想的区分点:被区分的两个分区中,数据的数量差不多。

两个常用的分区方法:

​ 1.三数取中法

​ 我们从区间的首、尾、中间,分别取出一个数,然后对比大小,取这 3 个数的中间值作为分区点。如果要排序的数组比较大,那“三数取中”可能就不够了,可能要“五数取中”或者“十数取中”。

​ 2.随机法

​ 随机法就是每次从要排序的区间中,随机选择一个元素作为分区点。这种方法并不能保证每次分区点都选的比较好,但是从概率的角度来看,也不大可能会出现每次分区点都选得很差的情况,所以平均情况下,这样选的分区点是比较好的。时间复杂度退化为最糟糕的 O(n2) 的情况,出现的可能性不大。

快速排序是用递归来实现的。我们在递归那一节讲过,递归要警惕堆栈溢出。为了避免快速排序里,递归过深而堆栈过小,导致堆栈溢出,我们有两种解决办法:第一种是限制递归深度。一旦递归过深,超过了我们事先设定的阈值,就停止递归。第二种是通过在堆上模拟实现一个函数调用栈,手动模拟递归压栈、出栈的过程,这样就没有了系统栈大小的限制。

时间复杂度代表的是一个增长趋势,如果画成增长曲线图,你会发现$O(n^2) $ 比 O(nlogn) 要陡峭,也就是说增长趋势要更猛一些。但是,我们前面讲过,在大 O 复杂度表示法中,我们会省略低阶、系数和常数,也就是说,O(nlogn) 在没有省略低阶、系数、常数之前可能是 O(knlogn + c),而且 k 和 c 有可能还是一个比较大的数。

假设 k=1000,c=200,当我们对小规模数据(比如 n=100)排序时,n2n^2的值实际上比 knlogn+c 还要小。

3.参考

这个是我学习王争老师的《数据结构与算法之美》所做的笔记,王争老师是前谷歌工程师,该课程截止到目前已有87244人付费学习,质量不用多说。

14.排序优化

截取了课程部分目录,课程结合实际应用场景,从概念开始层层剖析,由浅入深进行讲解。本人之前也学过许多数据结构与算法的课程,唯独王争老师的课给我一种茅塞顿开的感觉,强烈推荐大家购买学习。课程二维码我已放置在下方,大家想买的话可以扫码购买。

14.排序优化

本人做的笔记并不全面,推荐大家扫码购买课程进行学习,而且课程非常便宜,学完后必有很大提高。

14.排序优化