线性排序:如何根据年龄给100万用户数据排序?

news2025/1/18 3:24:47

文章来源于极客时间前google工程师−王争专栏。

桶排序、计数排序、基数排序时间复杂度是O(n),所以这类排序算法叫作线性排序。

线性的原因:三个算法是非基于比较的排序算法,都不涉及元素之间的比较操作。

三种排序对排序的数据要求苛刻,重点要掌握这些排序算法的适用场景

问题:如何根据年龄给100万用户排序?有没有更快的排序方法?

桶排序(Bucket sort)

核心思想:将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。
image

桶排序的时间复杂度为什么是O(n)呢?

如果要排序的数据有n个,我们把他们均匀地划分到m个桶内,每个桶里就有k=n/m个元素。每个桶里使用快速排序,时间复杂度为O(klogk)。m个桶的排序的时间复杂度就是O(mklogk),k=n/m,所以整个桶排序的时间复杂度就是O(nlong(n/m))。当桶的个数m接近数据个数n时,log(n/m)就是一个非常小的常量,这个时候时间复杂度就接近o(n)。

桶排序看起来很优秀,那它是不是可以替代我们之前讲的排序算法呢?

排序数据在各个桶之间的分布是比较均匀的。如果数据经过桶的划分之后,有些桶里的数据非常多,有些非常少,很不均匀,极端情况下,数据全部划分到一个桶里,就退化为O(nlogn)的排序算法了。

桶排序比较适合用在外部排序中。

外部排序就是数据存储在外部磁盘中,数据量比较大,内存有限,无法将数据全部加载到内存中。

问题:有10GB的订单数据,我们希望按订单金额(假设订单金额都是正整数)进行排序,但是我们的内存有限,只有几百MB,没办法一次性把10GB的数据都加载到内存中,这个时候该怎么办呢

我们可以借助桶排序的处理思想来解决这个问题。

我们可以先扫描一遍文件,看订单金额所处的数据范围。假设经过扫描之后我们得到,订单金额最小是1元,最大是10万元。我们将所有订单根据金额划分到100个桶里,第一个桶我们存储金额在1元到1000元之内的订单,第二桶存储金额在1001到2000元之内的订单,以此类推。每个桶对应一个文件,按照金额范围大小顺序编号。

理想情况下,如果订单金额在1到10万之间均匀分布,那么订单会被均匀划分到100个文件中,每个小文件中存储大约100MB的订单数据,可以放到内存中用快排来排序。

如果某个区间数据比较多,大小超过100MB,那么可以继续划分,直到所有的文件都能读入内存中为止。

计数排序(Counting sort)

**计数排序其实是桶排序的一种特殊情况。**当要排序的n个数据,所处范围并不大的时候,比如最大值是k,我们就可以把数据划分成k个桶。每个桶内的数据值都是相同的,省掉了桶内排序的时间。

高考分数查询名次系统。考生满分900分,最小0分,分成901个桶。每个桶都是分数相同的考生。依次扫描每个桶。将桶内考生依次输出到一个数组中,就实现了50万考生的排序。只涉及扫描遍历操作,所以时间复杂度是O(n)。

计数排序只不过是桶的大小粒度不同。为什么这个排序算法叫“计数”排序呢?“计数”的含义来自哪里呢

假设有8个考生,分数分别为2,5,3,0,2,3,0,3。分数在0~5分之间。放在一个A[8]的数组中。

我们使用大小为6的数组,下标表示分数,数组中的数值代表考生个数。
image

成绩为3分的考生在排序之后,会保存下标4,5,6的位置
image

如何计算出每个分数的考生在有序数组中对应的存储位置呢?处理方法非常巧妙

思路:对c[6]数组顺序求和,c[k]里存储小于等于分数k的考生个数。
image

步骤:依次扫描数组A。比如扫描到3,去C数组取出下标为3的值7,也就是到目前为止,包括自己在内,分数小于等于3的考生有7个,然后把3放到R中的第7个元素,下标为6。当3放入数组R中,小于等于3的元素就只剩下6个了,所以相应C[3]要减1,变成6。

image

代码实现如下:

// 计数排序,a是数组,n是数组大小。假设数组中存储的都是非负整数
	public static void countingSort(int[] a) {
		if (a == null) {
			return;
		}
		int n = a.length;
		if (n <= 1) {
			return;
		}
		// 统计a数组中的最大值
		int max = a[0];
		for (int i = 1; i < n; ++i) {
			if (a[i] > max) {
				max = a[i];
			}
		}
		// 初始化c数组 下标[0,max]
		int[] c = new int[max + 1];
		for (int i = 0; i <= max; ++i) {
			c[i] = 0;
		}
		// 统计数组a中,元素个数
		for (int i = 0; i < n; ++i) {
			c[a[i]]++;
		}
		// 数组c统计
		for (int i = 1; i <= max; ++i) {
			c[i] = c[i-1] + c[i];
		}
		// 构造临时数组r
		int[] r = new int[n];
		// 计数排序核心逻辑 遍历a数组
		for (int i = n - 1; i >= 0; --i) {
			r[c[a[i]] - 1] = a[i];
			c[a[i]]--;
		}
		// 将结果拷贝给a数组
		for (int i = 0; i < n; ++i) {
			a[i] = r[i];
		}
	}

总结:计数排序只能用在数据范围不大的场景中,如果数据范围k比要排序的数据n大很多,就不适合用计数排序了。而且,计数排序只能给非负整数排序,如果要排序的数据是其他类型的,要将其在不改变相对大小的情况下,转化为非负整数。

比如,还是考生的例子,如果考生成绩精确到小数后一位,我们就需要将所有分数先乘以10,转化成整数,然后再放到9010个桶内。如果要排序的数据中有负数,数据的范围是[-1000,1000],那我们就需要先对每个数据都加1000,转化成非负整数。

基数排序(Radix sort)

假设我们有10万个手机号码,希望将这10万个手机号码从小到大排序,你有什么比较快速的排序方法呢?

快排,时间复杂度nlogn,还有更高效的算法吗?桶排序、计数排序能派上用场吗?手机号码11位,范围太大,显然不适合用这两种排序算法。有没有时间复杂度是O(n)的算法呢?我们来看基数排序。

规律:两个号码,前面几位中较大的,后面几位就可以不看。

借助稳定排序算法。先按照最后一位来排序手机号码,然后再按照倒数第二位重新排序,以此类推,最后按照第一位重新排序。经过11次排序之后,手机号码就都有序了。

以字符串举例,如下图:
image

注意:如果是非稳定排序算法,最后一次排序只会考虑最高位的大小顺序,完全不管其他位的大小关系,那么低位的排序就完全没有意义了。

根据每一位排序,桶排序或者计数排序,时间复杂度可以做到n,排序数据有k位,需要k次桶排序或者计数排序,总的时间复杂度为O(k*n)。当k不大,手机号11位,复杂度接近O(n)。

排序数据不等长怎么办?可以把所有单词补齐到相同长度,位数不够的可以在后面补0,根据ASCII值,所有字母都大于0,所以补0没影响。

总结:基数排序对排序数据有要求,需要分割成独立的“位”来比较,而且位之间有递进的关系,如果a数据的高位比b数据大,那剩下的低位就不用比较了。除此之外,每一位的数据范围不能太大,要可以用线性排序算法来排序,否则基数排序的时间复杂度就无法做到O(n)了。

解答开篇

如何根据年龄给100万用户排序?

思路:类似按照成绩给50万考生排序。我们假设年龄范围最小1岁,最大不超过120岁。我们遍历这100万用户,根据年龄将其划分到这120个桶里,然后依次顺序遍历这120个桶中的元素。完成排序。

总结

学习了三种线性时间复杂度排序算法,桶排序、计数排序、基数排序。他们对排序的数据都有比较苛刻的要求,应用不是很广泛。但是如果数据特征比较符合这些排序算法的要求,应用这些算法,会非常高效,线性时间复杂度可以达到O(n)。

桶排序和计数排序的排序思想非常相似,都是针对范围不大的数据,将数据划分成不同的桶来实现排序。

基数排序要求数据可以划分成高低位,位之间有递进关系。比较两个数,我们只需要比较高位,高位相同再比较低位。而且每一位的数据范围不能太大,因为基数排序算法需要借助桶排序或者计数排序来完成每一位的排序工作。

思考

假设我们现在需要对D,a,F,B,c,A,z这个字符串进行排序,将所有小写排在大写前面。小写和大写内部不要求有序。如何实现?如果还有数字,怎么解决?

思路:用两个指针a、b:a指针从头开始往后遍历,遇到大写字母就停下,b从后往前遍历,遇到小写字母就停下,交换a、b指针对应的元素;重复如上过程,直到a、b指针相交。

对于小写字母放前面,数字放中间,大写字母放后面,可以先将数据分为小写字母和非小写字母两大类,进行如上交换后再在非小写字母区间内分为数字和大写字母做同样处理

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

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

相关文章

19 | 如何搞清楚事务、连接池的关系?正确配置是怎样的

事务的基本原理 在学习 Spring 的事务之前&#xff0c;你首先要了解数据库的事务原理&#xff0c;我们以 MySQL 5.7 为例&#xff0c;讲解一下数据库事务的基础知识。 我们都知道 当 MySQL 使用 InnoDB 数据库引擎的时候&#xff0c;数据库是对事务有支持的。而事务最主要的作…

(转)富文本编辑器——Vue2Editor

介绍 Vue2Editor是一个简单易用且功能强大的Vue版本的富文本编辑器&#xff0c;其基于Quill.js和Vuejs构建&#xff01; 简单易用、功能强大的富文本编辑器——Vue2Editor Github https://github.com/davidroyer/vue2-editor 特性 简单易用&#xff1b;基于Vue.js & Quil…

【Golang】Go的并发和并行性解释。谁说Go不是并行语言?

偶然发现百度上有很多"师出同门"的"go是并发语言&#xff0c;而不是并行语言"的说法。让我顿感奇怪&#xff0c;"并行"说白了就是对CPU多核的利用&#xff0c;这年头不能利用多核的编译语言还有的混&#xff1f;而且还混的这么好&#xff1f;并且…

Linux网络编程系列之服务器编程——非阻塞IO模型

Linux网络编程系列 &#xff08;够吃&#xff0c;管饱&#xff09; 1、Linux网络编程系列之网络编程基础 2、Linux网络编程系列之TCP协议编程 3、Linux网络编程系列之UDP协议编程 4、Linux网络编程系列之UDP广播 5、Linux网络编程系列之UDP组播 6、Linux网络编程系列之服务器编…

echarts关于一次性绘制多个饼图 (基于vue3)

在echarts中&#xff0c;dataset 和 source 是用来配置数据的选项。 dataset 是一个包含数据相关配置的对象&#xff0c;用于指定数据的来源和格式。它可以包含多个维度的数据集&#xff0c;每个维度都可以有自己的名称和数据。 source 是 dataset 中的一个子项&#xff0c;用于…

图计算(林子雨慕课课程)

文章目录 13. 图计算13.1 图计算简介13.2 Pregel简介13.3 Pregel图计算模型13.3.1 有向图和顶点13.3.2 Pregel的计算过程13.3.2 Pregel实例 13.4 Pregel的C API13.4.1 定义Vertex基类13.4.2 消息传递机制和Combiner13.4.3 Aggregator、拓扑改变和输入输出 13.5 Pregel的体系结构…

【通过实验带你认识linux下的源码编译】

通过实验带你认识linux下的源码编译 01 初识项目编译02 编译过程03 完整的编译过程1、创建源代码文件2、创建configure脚本3、创建Makefile.am 源代码是相对目标代码和可执行代码而言的。源代码是用汇编语言和高级语言写出来的代码。 目标代码是指源代码经过编译程序产生的能被…

qml介绍

文章目录 qml简介对象一个风车的例子 qml简介 从 Qt 4.7 开始&#xff0c;Qt 引入了一种声明式脚本语言&#xff0c;称为 QML&#xff08;Qt Meta Language 或者 Qt Modeling Language&#xff09;&#xff0c;作为 C 语言的一种替代。而 Qt Quick 就是使用 QML 构建的一套类库…

(latex中appendix附录怎么写)以及(附录里面的图片表格之类的如何重新编号)

文章目录 初级&#xff1a;怎么写进阶&#xff1a;怎么重新编号进阶&#xff1a;怎么换成单栏格式 初级&#xff1a;怎么写 这个很简单&#xff0c;我一开始以为很复杂。 \begin{document} #这里是“正文”。 #这里是“引用”。 #下面开始是附录。 \appendix \section{Proofs…

英语——分享篇——每日100词——801-900

medical——adj.医疗的——me我(熟词)di弟(拼音)cal擦了(拼音) chief——n.酋长——thief小偷——小偷拜见酋长 pork——n.猪肉——p皮鞋(编码)or偶人(拼音)k机关枪(编码)——穿着皮鞋的偶人扛着机关枪挑猪肉 pie——n.馅饼&#xff0c;派——瞥——他无意瞥见一块馅饼 saus…

GB28181平台简介

产品简介 LiveMedia视频中间件是支持部署到本地服务器或者云服务器的纯软件服务&#xff0c;也提供服务器、GPU一体机全包服务&#xff0c;提供视频设备管理、无插件、跨平台的实时视频、历史回放、语音对讲、设备控制等基础功能&#xff0c;支持视频协议有海康、大华私有协议…

Golang学习记录:基础篇练习(一)

Golang学习记录&#xff1a;基础篇练习&#xff08;一&#xff09; 1、九九乘法表2、水仙花数3、斐波那契数列4、编写一个函数&#xff0c;求100以内的质数5、统计字符串里面的字母、数字、空格以及其他字符的个数6、二维数组对角线的和7、冒泡排序算法8、选择排序算法9、二分查…

JDK 19 协程新特性学习

目录 一、协程定义 二、协程发展史 &#xff08;一&#xff09;协程的基本发展史说明 &#xff08;二&#xff09;Java协程发展说明 三、JDK 19 协程的原理细节 &#xff08;一&#xff09;Thread.ofVirtual().start() &#xff08;二&#xff09;SocketChannel.write(…

没有前端如何测试后端跨域问题

一、问题 前段时间对项目中的跨域做了相关的处理&#xff0c;网上有很多跨域的解决方案。前端解决&#xff0c;后端解决&#xff0c;nginx代理解决。我采用的是在后端中使用Cors来解决跨域的问题。但是前端项目还没有搭建起来&#xff0c;并不知道Cors的解决方案是否会生效&am…

揭秘元宇宙背后最炫科技风:数字经济时代,元宇宙发展解决方案及核心技术

文章目录 前言一、关于“元宇宙”业界趋势1.1、元宇宙的概念与发展历程1.2、行业应用体验向虚实融合和实时互动演进1.3、数字内容成为各行业 3D 数字世界入口 二、对于元宇宙发展的解决方案和实践2.1、MetaStudio 构建场景化全栈能力2.2、企业 3D 空间&#xff0c;围绕 4 类场景…

PyQt界面里如何加载本地视频以及调用摄像头实时检测(小白入门必看)

目录 1.PyQt介绍 2.代码实现 2.1实时调用摄像头 2.2 使用YOLOv5推理 2.3 代码中用到的主要函数 1.PyQt介绍 PyQt是一个用于创建桌面应用程序的Python绑定库&#xff0c;它基于Qt框架。Qt是一个跨平台的C应用程序开发框架&#xff0c;提供了丰富的图形界面、网络通信、数据…

Qt项目通过.pri文件将众多文件按功能模块分类显示,开发大型项目必备

Qt项目通过.pri文件将众多文件按功能模块分类显示&#xff0c;开发大型项目必备 Chapter1 Qt项目通过.pri文件将众多文件按功能模块分类显示&#xff0c;开发大型项目必备($$$)Chapter2 在Qt项目中添加pri文件前言创建pri文件的步骤一、创建Qt项目二、创建pri空文件三、调试 Ch…

SpringCloud学习笔记-Nacos服务分级存储模型

Nacos服务分级存储模型 一级是服务&#xff0c;例如userservice二级是集群&#xff0c;例如杭州或上海三级是实例&#xff0c;例如杭州机房的某台部署了userservice的服务器 微服务互相访问时&#xff0c;应该尽可能访问同集群实例&#xff0c;因为本地访问速度更快。当本集…

创新YOLOv8改进:结合全新可变形大核注意力(D-LKA Attention)实现多尺度目标涨点

🔥🔥🔥 提升多尺度目标检测,创新提升 🔥🔥🔥 🔥🔥🔥 捕捉图像特征和处理复杂图像特征 🔥🔥🔥 👉👉👉: 本专栏包含大量的新设计的创新想法,包含详细的代码和说明,具备有效的创新组合,可以有效应用到改进创新当中 👉👉👉: 🐤🐤�…

寻找AI时代的关键拼图,从美国橡树岭国家实验室读懂AI存力信标

超算&#xff0c;是计算产业的明珠&#xff0c;是人类探索未知的航船。超算的发展与变化&#xff0c;不仅代表着各个国家与地区间的科技竞争力&#xff0c;更将作为趋势风向标&#xff0c;影响整个数字化体系的走向。 在目前阶段&#xff0c;超算与AI计算的融合是大势所趋。为了…