【硬核】小明高考考了680分,他想知道在全国1000W考生中排什么名次?
大家好,我是Tom哥~
有这么一道题:
今年高考,有1000万的考生参加了考试,满分750,小明靠了680分,他想知道自己的全国排名,如何快速排序呢?
这里面会给大家介绍几种算法:
一、桶排序
算法思想
将要排序的数据拆分、分组放入几个有序
的桶里,然后分别对每一个桶中的元素排序,最后将桶中的元素依次取出,就完成了最终的排序。
例子:
对 9,8,19,2,7,15,20,6,4,1,11,17 等数字排序。(为了简化描述,这里只列举了12个数)
其中,最小值是 1,最大值是 20。整个区间最大跨度是 20,我们将其分成了4个桶,然后再采用
快速排序
对每个桶里的元素排序。
如果待排序的数据是m个,均匀的分到n个桶中,每个桶中的元素个数 j=m/n
每个桶采用快速排序,时间复杂度是 O(j*log(j))
,所有桶的时间复杂度是 O(n*j*log(j))
整理后,该算法的时间复杂度是 O(m*log(m/n))
如果桶个数n无限接近m,那桶排序的时间复杂度近似 O(m)
适用场景
1、数据均匀分布,没有严重倾斜。否则,很容易发生大部分数据集中在某几个桶中
2、桶容易划分,如:手机号排序就不太适合
3、桶与桶之间天然有序,不需要再单独排序
4、一些特殊的场景,比如数据文件很大,有几十个G,内存无法一次全部加载,可以考虑分桶,分批排序
如果桶中的数据分布不均匀怎么办?
一图胜千言,“拆”字万里行,大事化小,小事化了。
我们对原始数据分组选桶时,可以为每个桶设定一个计数器,当发现某个分桶的数据量偏大时,可以考虑将该桶二次拆分为若干子桶。
当然,如果子桶的数据量还是很大,我们可以进一步拆分为子子桶。
拆分的深度,可以结合具体业务情况,自己把控。
如果排序的数据的范围不大(比如人的年龄、考试成绩),采用先分桶再快速排序有些浪费,我们可以采用下面这种排序方式。
二、计数排序
计数排序的要求是排序的数据范围不大,比如有m个数,其中最大值是i,那么可以分成i个桶,每个桶里的数据都是相同的,这样就省掉了对桶内元素的排序。
举个典型的场景:
我们都参加过高考,今年有1000万的考生参加考试,满分750,小明考了680分,他想知道自己的全国排名,如何快速排序呢?
满分750,考生的分数最小可能是0分,最高是750分,所以我们就分为了 751 个桶,按分数将考生放入对应的桶中。
然后,再依次读取每个桶中的数据,写入一个数组中,便得到了 1000万考生的分数排名。
小明考了680分,他想快速知道自己的排名,如何实现?
方案一:遍历排序好的数组,由于是由大到小且有序的,我们找到第一个680的元素,便得到最终的排名。
方案二:对算法进行优化,每个桶配备一个计数器
,桶中每添加一个元素,计数器加一。这样,你只需要将681~750 之间的桶的计数器累计求和,便得到最终的排名。
三、基数排序
上面的两种排序,还是很好理解的,可以解决很多业务场景问题。
但如果是对若干数量的手机号由小到大排序,怎么解决呢?
我们知道,手机号是11位,范围太大,而桶排序和计数排序,对数据范围有较高要求
,显然手机号不太合适。
这里介绍一种新的排序算法,基数排序。
核心思想
先比较高位的值,如果a的高位大于b的高位,那么a肯定是大于b的,后面的低位值就可以不用比较。
比如:对下面的若干英文名做排序
解题思路,如上图所示
首先,对每个名称的第一个字母做排序,可以采用分桶或计数排序。
同一个桶内的元素,然后提取第二个字母,再次分桶或计数排序,
循环遍历,直到比较完第11位,
当然,比较期间,如果某个阶段,桶中的元素只有一个,那么该阶段可以终止。
有点类似上面的《如果桶中的数据分布不均匀怎么办?》解决思路。
特别注意:
上面排序的英文名字长度可能不同,我们先要做数据预处理,取最大的长度,将位数不够的后面补"0"。
因为ASCII 值,最小的是字母A,对应的十进制是65,也是大于0。所以,补“0” 不影响排序。
适用场景
如果整体比较难度较大,数值的区间较大,不适合分桶。我们可以考虑采用分割的思想,分割出若干小段,依次对小段来比较,各个小段存在递进关系。
关注公众号「微观技术」,后台回复 “算法” ,免费领取资料
推荐阅读
团队管理那点破事!OKR绩效、核心人才、面试、技术分享、研发流程....
MYSQL 那点破事!索引、SQL调优、事务、B+树、分表 ....
TCP网络那点破事!三次握手、四次挥手、TIME-WAIT ....