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

问题:平时的开发中,我们都是直接使用这些现成的函数来实现业务逻辑中的排序功能。这些排序函数是如何实现的吗?底层都利用了哪种排序算法呢?比如 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() 插入排序的算法实现中,也利用了哨兵来简化代码,提高执行效率这种编程技巧。虽然哨兵可能只是少做一次判断,但是毕竟排序函数是非常常用、非常基础的函数,性能的优化要做到极致

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/419116.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

15 | 二分查找(上):如何用最省内存的方式实现快速查找功能?

思考题:假设有 1000 万个整数数据,每个数据占 8 个字节,如何设计数据结构和算法,快速判断某个整数是否出现在这 1000 万数据中?希望不要占用太多的内存空间,最多不要超过 100MB 二分思想 查找算法 …

16 | 二分查找(下):如何快速定位IP对应的省份地址?

问题:假设我们有 12 万条这样的 IP 区间与归属地的对应关系,如何快速定位出一个 IP 地址的归属地呢? 二分查找的变形问题: 变体一:查找第一个值等于给定值的元素 public int bsearch(int[] a, int n, int value) {int…

UVA 11557 - Code Theft (KMP + HASH)

UVA 11557 - Code Theft 题目链接 题意:给定一些代码文本。然后在给定一个现有文本,找出这个现有文本和前面代码文本,反复连续行最多的这些文本 思路:把每一行hash成一个值。然后对于每个文本计算最大匹配值,枚举后缀。…

上传图片预览,支持IE6

//说明:图片上传预览插件 //上传的时候可以生成固定宽高范围内的等比例缩放图//参数设置: //width 存放图片固定大小容器的宽 //height 存放图片固定大小容器的高 //imgDiv 页面DIV的JQuery的…

17 | 跳表:为什么Redis一定要用跳表来实现有序集合?

问题:如果数据存储在链表中,就真的没法用二分查找算法了吗?可以对链表进行“改造”,就可以支持类似“二分”的查找算法。 跳表 定义:对链表经过改造之后的数据结构叫做跳表(Skip list)&#x…

亲和数 杭电2040

古希腊数学家毕达哥拉斯在自然数研究中发现,220的所有真约数(即不是自身的约数)之和为: 1245101120224455110=284。 而284的所有真约数为1、2、4、71、 142,加起来恰好为220。人们对这样的数感到很惊奇,并称之为亲和数…

18 | 散列表(上):Word文档中的单词拼写检查功能是如何实现的?

问题引入 在 Word 里输入一个错误的英文单词,它就会用标红的方式提示“拼写错误”,Word 文本编辑器的拼写检查功能是如何实现的呢?散列表(Hash Table) 散列表 散列表定义:散列表的英文叫“Hash Table”&…

19 | 散列表(中):如何打造一个工业级水平的散列表?

问题引入:如何实现一个工业级的散列表? 主要要求: 设计一个合适的散列函数;定义装载因子阈值,并且设计动态扩容策略;选择合适的散列冲突解决方法。 对于动态散列表来说,不管我们如何设计散列函…

RTTI: dynamic_cast typeid

dynamic_cast:将基类类型的指针向派生类指针安全转换。多用于下行转换。上行转换时,和static_cast是一样的。C类型转换看这里。而const_cast用来修改类型的const或volatile属性。。。下面主要说多态下的RTTI: 使用条件:  基类应有虚函数。 …

20 | 散列表(下):为什么散列表和链表经常会一起使用?

有两种数据结构,散列表和链表经常会被放在一起使用。常见的使用方式有: 用链表来实现 LRU 缓存淘汰算法,链表实现的 LRU 缓存淘汰算法的时间复杂度是 O(n),通过散列表可以将这个时间复杂度降低到 O(1)。Redis 的有序集合是使用跳…

“睡服”面试官系列第二十三篇之修饰器(建议收藏学习)

目录 1. 类的修饰 2. 方法的修饰 3. 为什么修饰器不能用于函数? 4. core-decorators.js 4.1autobind 4.2readonly 4.3override 4.4deprecate (别名deprecated) 4.5suppressWarnings 5. 使用修饰器实现自动发布事件 6. Mixin 7. Trait 8. Babel 转码器的…

21 | 哈希算法(上):如何防止数据库中的用户信息被脱库?

问题:对于用户信息中的密码,你会如何存储用户密码?仅仅 MD5 加密一下存储就够了吗?——哈希算法 什么是哈希算法 哈希算法的定义和原理:将任意长度的二进制值串映射为固定长度的二进制值串,这个映射的规则…

冬季小学期 NIIT公司 web前端培训 CSS

外边距合并 http://www.w3school.com.cn/css/css_margin_collapsing.asp div、p、h1 块元素 span行元素 浮动:浮动的框可以向左或向右移动,直到它的外边缘碰到包含框或另一个浮动框的边框为止。 http://www.w3school.com.cn/css/css_positioning_floatin…

22 | 哈希算法(下):哈希算法在分布式系统中有哪些应用?

本节分析哈希算法的其他三个应用:负载均衡、数据分片、分布式存储。这三个应用都跟分布式系统有关。看下哈希算法是如何解决这些分布式问题的。 五:负载均衡 问题:那如何才能实现一个会话粘滞(session sticky)的负载…

BZOJ 1491: [NOI2007]社交网络( floyd )

floyd...求最短路时顺便求出路径数. 时间复杂度O(N^3) -------------------------------------------------------------------------------------------#include<cstdio>#include<algorithm>#include<cstring>using namespace std;typedef long long ll;con…

前端学习(1731):前端系列javascript之发布窗口布局下

index.vue <template><view class"content"><view class"todo-header"><view class"todo-header_left"><text class"active-text">全部</text><text>10条</text></view><v…

你还不会手写SpringBoot启动器吗

Starter是什么 &#xff1f; Spring Boot 对比 Spring MVC 最大的优点就是使用简单&#xff0c;约定大于配置。不用 Spring MVC 的时候&#xff0c;时不时被 xml 配置文件搞的晕头转向&#xff0c;冷不防还因为 xml 配置上的一点疏忽&#xff0c;导致整个项目莫名其妙的不可用…