分析角度
时间复杂度
我们知道时间复杂度最好的排序算法是O(N),比如桶排序跟计数排序,其次就是O(nlogn),比如快速排序跟归并排序,那么我们实现一个通用的排序函数应该选择哪种时间复杂度的算法?
答案是O(nlogn) 因为O(n)的排序算法只适用于某一些特殊的业务场景
空间复杂度
空间复杂度最好的是O(1),也就是原地排序算法,比如快速排序 插入排序等
那对于空间复杂度的选择我们肯定是优先选择原地排序算法,不过对于数据量小的情况下我们应该选择空间换时间的算法,比如空间复杂度为O(n)且复杂度稳定O(nlogn)的归并算法
时间复杂度的系数、常数 、低阶
我们知道,时间复杂度反映的是数据规模 n 很大的时候的一个增长趋势,所以它表示的时候会忽略系数、常数、低阶。但是实际的软件开发中,我们排序的可能是 10 个、100 个、1000 个这样规模很小的数据,所以,排序算法性能对比的时候,我们就要把系数、常数、低阶也考虑进来。
排序算法的稳定性
这个概念是说,如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。我通过一个例子来解释一下。比如我们有一组数据 2,9,3,4,8,3,按照大小排序之后就是 2,3,3,4,8,9。这组数据里有两个 3。经过某种排序算法排序之后,如果两个 3 的前后顺序没有改变,那我们就把这种排序算法叫作稳定的排序算法;如果前后顺序发生变化,那对应的排序算法就叫作不稳定的排序算法,当然这个得跟据具体业务来定
qsort()高性能排序函数
有了思路让我们看看c语言中的qsort实现思路
qsort() 会优先使用归并排序来排序输入数据,因为归并排序的空间复杂度是 O(n),所以对于小数据量的排序,比如 1KB、2KB 等,归并排序额外需要 1KB、2KB 的内存空间,这个问题不大。用空间换时间的技巧。但如果数据量太大,排序 100MB 的数据,这个时候我们再用归并排序就不合适了。所以,要排序的数据量比较大的时候,qsort() 会改为用快速排序算法来排序。
递归太深会导致堆栈溢出的问题,qsort() 是通过自己实现一个堆上的栈,手动模拟递归来解决的。
qsort() 还用到插入排序。在快速排序的过程中,当要排序的区间中,元素的个数小于等于 4 时,qsort() 就退化为插入排序,不再继续用递归来做快速排序,在小规模数据面前,O(n2) 时间复杂度的算法并不一定比 O(nlogn) 的算法执行时间长
问题一、为什么选择插入而不是其他的冒泡?
其实在我们封装一个通用函数的时候,我们是想要将性能达到极致的(比如qsort使用了哨兵减少了一次判断),冒泡的交换代码会比插入多而选择排序则最好最坏都是o(n2),插入最好是o(n)。
总结
qsort()只是一个实现的示例,我们也可以通过上面的思考角度来实现或者引入符合我们业务独特的排序函数,比如是否可以考虑桶排序这些O(n)的高性能排序算法,以及业务是否要追求稳定性等等。