高性能排序函数实现方案

简介: 如C语言的qsort()、Java的Collections.sort(),这些排序函数如何实现?

如C语言的qsort()、Java的Collections.sort(),这些排序函数如何实现?


1 合适的排序算法?


1.png

线性排序算法的时间复杂度较低,适用场景特殊,通用排序函数不能选择。


小规模数据排序,可选时间复杂度O(n^2)算法

大规模数据排序,时间复杂度O(nlogn)算法更高效

为兼顾任意规模数据的排序,一般首选时间复杂度O(nlogn)排序算法:堆排、快排都有较多应用,如JDK采用堆排实现排序函数,C使用快排。


2 归排分析

使用归排情况不多。快排最坏时间复杂度O(n^2),而归排能做到平均、最坏时间复杂度都是O(nlogn),看起来诱人,为何没被“宠信”?


归排不是原地排序算法,空间复杂度O(n)。粗略夸张点讲,待排序100MB数据,除数据本身占用内存,排序还额外再占100MB内存空间,空间耗费翻倍。


快排更适合实现排序,但快排最坏时间复杂度O(n2)。


3 优化快排


数据原来就有序或接近有序,每次分区点都选择最后一个数据,则快排就很差,时间复杂度退化为O(n2)。主要还是分区点不合理。


最理想分区点

被分区点分开的两个分区数据量差不多。为提高排序算法性能,尽可能让每次分区都平均:


3.1 三数取中法

从区间的首、尾、中,分别取个数,对比大小,取这3数中间值作为分区点。


这样每隔某固定长度,取数出来比较,将中间值作为分区点,比纯粹取某数据好。但若排序数组较大,则“三数取中”可能就不够,可能“五数取中”或“十数取中”。


3.2 随机法

每次从待排序区间,随机选一个元素作为分区点。


这不能保证每次分区点都选得好,但也不大可能每次分区点都选得差,平均情况下,这样选分区点较好。时间复杂度退化为最糟糕的O ( n 2 ) O(n2)O(n2)情况概率不大。


快排用递归实现,而递归要避免堆栈溢出:


限制递归深度

一旦递归过深,超过设定阈值,就停止递归

在堆上模拟实现一个函数调用栈

手动模拟递归压栈、出栈的过程,这样就没有了系统栈大小的限制。


4 总结


如Glibc的qsort()函数,名字很像基于快排,实际并不仅用快排。


qsort()优先使用归排,因归排空间复杂度O ( n ) O(n)O(n),对小数据量排序,额外所需内存空间不大,即空间换时间。


但若数据量太大,归排不合适。改为快排。qsort()如何选择快排分区点?“三数取中法”。


递归太深会导致堆栈溢出,qsort()自己实现一个堆上的栈,手动模拟递归来解决。qsort()不仅用到归排、快排,还用到插排。快排过程中,当要排序的区间中,元素个数≤4,qsort()就退化为插排,不再续用递归做快排,因为小规模数据,O ( n 2 ) O(n2)O(n2)时间复杂度算法不一定比O ( n l o g n ) O(nlogn)O(nlogn)的算法执行时间长。


算法性能可通过时间复杂度分析,但这种复杂度分析较偏理论,实际上时间复杂度并不等于代码实际的运行时间。


时间复杂度代表的是增长趋势,画成增长曲线图,发现O ( n 2 ) O(n2)O(n2)比O ( n l o g n ) O(nlogn)O(nlogn)增长趋势更猛。大O复杂度表示法中,会省略低阶、系数和常数,即O(nlogn)在没有省略低阶、系数、常数之前可能是O(knlogn + c),而k和c有可能还是个较大的数。


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

k n l o g n + c = 1000 ∗ 100 ∗ l o g 100 + 200 > > 10000 knlogn+c = 1000 * 100 * log100 + 200 >> 10000knlogn+c=1000∗100∗log100+200>>10000

n 2 = 100 ∗ 100 = 10000 n^2 = 100*100 = 10000n

2

=100∗100=10000


所以,小规模数据排序,O ( n 2 ) O(n2)O(n2)排序算法不一定比O ( n l o g n ) O(nlogn)O(nlogn)执行更久。小数据量排序,选择更简单、无需递归的插排。


哨兵来提高执行效率,在qsort()插入排序的算法实现中,虽然哨兵可能只是少做一次判断,但是毕竟排序函数是非常常用、非常基础的函数,性能的优化要做到极致。

目录
相关文章
|
3天前
|
存储 数据处理 C语言
NumPy 通用函数(ufunc):高性能数组运算的利器
NumPy的通用函数(ufunc)提供高性能的逐元素运算,支持向量化操作和广播机制,能应用于数组的数学、逻辑和比较运算。ufunc可提高计算速度,避免低效的循环,并允许自定义函数以满足特定需求。例如,ufunc实现加法比循环更高效。通过`frompyfunc`可创建自定义ufunc。判断函数是否为ufunc,可检查其类型是否为`numpy.ufunc`。ufunc练习包括数组的平方、平方根、元素积及性能对比。
15 0
|
2天前
|
存储 算法
数据结构和算法——散列表的性能分析(开放地址法的查找性能、期望探测次数与装填因子的关系、分离链接法的查找性能)
数据结构和算法——散列表的性能分析(开放地址法的查找性能、期望探测次数与装填因子的关系、分离链接法的查找性能)
7 0
|
24天前
|
分布式计算 并行计算 算法
图计算中的性能优化有哪些方法?请举例说明。
图计算中的性能优化有哪些方法?请举例说明。
22 0
|
10月前
|
存储 Java 测试技术
4.3 Java数组性能优化策略:数组与集合性能对比分析
4.3 Java数组性能优化策略:数组与集合性能对比分析
104 0
|
JSON 安全 程序员
GoFrame的gmap相比Go原生的map,天然支持排序和有序遍历
这篇文章就是给初学的小伙伴们答疑解惑的,会为大家介绍: 为什么Go语言中的map是无序的,如何自定义实现map的排序?
163 0
GoFrame的gmap相比Go原生的map,天然支持排序和有序遍历
|
存储 人工智能 监控
函数计算使用场景
函数计算使用场景
236 0
|
搜索推荐 算法 C语言
排序优化:如何实现一个通用的、高性能的排序函数?
如何选择合适的排序算法? 如果要实现一个通用的、高效率的排序函数,我们应该选择哪种排序算法?我们先回顾一下前面讲过的几种排序算法。
160 0
排序优化:如何实现一个通用的、高性能的排序函数?
|
关系型数据库 PostgreSQL 移动开发
PostgreSQL 9.6 聚合运算180倍性能提升如何做到? 聚合代码优化OP复用浅析
PostgreSQL 9.6 内核优化之 聚合代码优化OP复用浅析 作者 digoal 日期 2016-10-08 标签 PostgreSQL , 9.6 , 内核优化 , 聚合代码优化 , OP复用 背景 聚合操作指将分组的数据聚合为一个结果输出。 聚合通常用在统
5309 0
|
前端开发
两个数组数据的高效合并方案
作为一个前端,服务器返回的数据易用,能极大的提升开发效率。 能一个接口提供的数据,就不要用去调用两次或者更多网络请求,然后进行数据合并。 然而,理想和现实两者,现实总是找我,感觉不到理想对的温暖。
224 0
两个数组数据的高效合并方案
|
SQL 存储 索引
Greenplum 优化CASE - 对齐JOIN字段类型,使用数组代替字符串,降低字符串处理开销,列存降低扫描开销
标签 PostgreSQL , 数组 , 字符串 , 字符串处理 , JOIN , where , 类型一致性 背景 Greenplum通常被用作OLAP,在一些用户使用过程中,可能因为数据结构设计,SQL问题等原因导致性能不佳,虽然通过增加节点可以解决问题,但是如果能优化的话,可以节约不少硬件资源。
1504 0

热门文章

最新文章