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

这篇博客深入探讨了不同编程语言中排序函数的实现,如C语言的qsort()、C++的sort()和stable_sort()以及Java的Collections.sort()。文章指出,通用的排序函数通常选择时间复杂度为O(nlogn)的算法,如堆排序和快速排序。尽管归并排序在最坏情况下时间复杂度稳定,但由于其空间复杂度高,未被广泛采用。快排通过合理的分区点选择(如三数取中法、随机法)和递归深度限制来优化。C语言的qsort()在小数据量时使用归并排序,大数据量时切换到快速排序,并在元素数量小于等于4时退化为插入排序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题:平时的开发中,我们都是直接使用这些现成的函数来实现业务逻辑中的排序功能。这些排序函数是如何实现的吗?底层都利用了哪种排序算法呢?比如 C 语言中 qsort(),C++ STL 中的 sort()、stable_sort(),还有 Java 语言中的 Collections.sort()

希望你把思考的过程看得比标准答案更重要

如何选择合适的排序算法?

  • 线性排序算法的时间复杂度比较低,适用场景比较特殊。所以如果要写一个通用的排序函数,不能选择线性排序算法
  • 如果对小规模数据进行排序,可以选择时间复杂度是 O(n2) 的算法
  • 如果对大规模数据进行排序,时间复杂度是O(nlogn) 的算法更加高效

总结:为了兼顾任意规模数据的排序,一般都会首选时间复杂度是 O(nlogn) 的排序算法来实现排序函数。堆排序的时间复杂度是 O(nlogn),堆排序和快速排序都有比较多的应用,比如 Java 语言采用堆排序实现排序函数,C 语言使用快速排序实现排序函数。

快排在最坏情况下的时间复杂度是 O(n2),而归并排序可以做到平均情况、最坏情况下的时间复杂度都是 O(nlogn),为什么归并排序没得到普遍的使用?

原因:归并排序并不是原地排序算法,空间复杂度是 O(n)。所以,粗略点、夸张点讲,如果要排序 100MB 的数据,除了数据本身占用的内存之外,排序算法还要额外再占用 100MB 的内存空间,空间耗费就翻倍了

如何优化快排

快排在原数据有序的情况下,如果每次分区点都选择最后一个数据,那么时间复杂度会退化为O(n2)。实际上,这种 O(n2) 时间复杂度出现的主要原因还是因为我们分区点选得不够合理。什么样的分区点合理?

最理想的分区点是:被分区点分开的两个分区中,数据的数量差不多。比较常用的分区算法:

  • 三数取中法(如果数组较大,可能需要五数取中、十数取中):从区间的首、尾、中间,分别取出一个数,然后对比大小,取这 3 个数的中间值作为分区点
  • 随机法:每次从要排序的区间中,随机选择一个元素作为分区点。不保证每次分区点都选的很好,但是从概率角度推理,不大可能出现每次分区都很差的情况,平均情况下,分区点还是比较好的
  • 快排通过递归来实现的,要警惕堆栈溢出。解决方法:一限制递归深度,一旦递归过深超过阈值,停止递归;二是通过在堆上手动模拟一个函数调用栈,手动模拟递归压栈、出栈的过程,摆脱系统栈的大小限制

Glibc 中的 qsort() 函数举例

源码分析:

  • qsort() 会优先使用归并排序来排序输入数据:因为归并排序的空间复杂度是 O(n),所以对于小数据量的排序,额外的空间问题不大,通过空间换时间
  • 要排序的数据量比较大的时候,qsort() 会改为用快速排序算法来排序。如何选择分区点:源码中采用的是“三数取中法”,另外qsort() 是通过自己实现一个堆上的栈,手动模拟递归来解决的
  • qsort() 并不仅仅用到了归并排序和快速排序,它还用到了插入排序。当要排序的区间中,元素的个数小于等于 4 时,qsort() 就退化为插入排序:因为在小规模数据面前,O(n2) 时间复杂度的算法并不一定比 O(nlogn) 的算法执行时间长
  • 对于小规模数据的排序,O(n2) 的排序算法并不一定比 O(nlogn) 排序算法执行的时间长。对于小数据量的排序,我们选择比较简单、不需要递归的插入排序算法
  • 此外,在 qsort() 插入排序的算法实现中,也利用了哨兵来简化代码,提高执行效率这种编程技巧。虽然哨兵可能只是少做一次判断,但是毕竟排序函数是非常常用、非常基础的函数,性能的优化要做到极致

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值