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

文章来源于极客时间前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];}// 构造临时数组rint[] 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.rhkb.cn/news/159071.html

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

相关文章

CCF CSP认证 历年题目自练Day30

题目一 试题编号&#xff1a; 202203-1 试题名称&#xff1a; 未初始化警告 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 512.0MB 问题描述&#xff1a; 题目背景 一个未经初始化的变量&#xff0c;里面存储的值可能是任意的。因此直接使用未初始化的变量&#xff0c;比…

太强了,真的太强了!

国庆之后gpt4上线了很多强大的功能&#xff0c;有超级强大的数据分析和挖掘的功能&#xff0c;有可以比肩AI绘图神器Midjourney的绘图功能&#xff08;前面写了一篇泰酷辣&#xff01;目前最强的AI绘画神器&#xff01;文生图模型 DALLE 3来啦&#xff01;&#xff09;&#xf…

Python正则表达式

正则表达式 当处理文本数据时&#xff0c;正则表达式是一种强大的工具&#xff0c;它允许我们根据特定的模式来匹配、搜索和处理字符串。 正则表达式由一系列字符和特殊字符组成&#xff0c;用于描述文本模式。这些模式可以包含普通字符&#xff08;如字母、数字和标点符号&a…

【TensorFlow2 之012】TF2.0 中的 TF 迁移学习

#012 TensorFlow 2.0 中的 TF 迁移学习 一、说明 在这篇文章中&#xff0c;我们将展示如何在不从头开始构建计算机视觉模型的情况下构建它。迁移学习背后的想法是&#xff0c;在大型数据集上训练的神经网络可以将其知识应用于以前从未见过的数据集。也就是说&#xff0c;为什么…

蓝桥杯 第 1 场算法双周赛 第1题 三带一 c++ map 巧解 加注释

题目 三带一【算法赛】https://www.lanqiao.cn/problems/5127/learning/?contest_id144 问题描述 小蓝和小桥玩斗地主&#xff0c;小蓝只剩四张牌了&#xff0c;他想知道是否是“三带一”牌型。 所谓“三带一”牌型&#xff0c;即四张手牌中&#xff0c;有三张牌一样&#…

CSS学习基础知识

CSS学习笔记 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width,…

独立式三相无源逆变电源设计

摘要 面对全球日趋严重的能源危机问题&#xff0c;可再生能源的开发和利用得到了人们的高度重视。其中辐射到地球太阳能资源是十分富饶的&#xff0c;绿色清洁的太阳能不会危害我们的生存环境&#xff0c;因而受到了人们的广泛利用。光伏发电作为可再生能源被广泛的应用&#x…

RabbitMq启用TLS

Windows环境 查看配置文件的位置 选择使用的节点 查看当前节点配置文件的配置 配置TLS 将证书放到同配置相同目录中 编辑配置文件添加TLS相关配置 [{ssl, [{versions, [tlsv1.2]}]},{rabbit, [{ssl_listeners, [5671]},{ssl_options, [{cacertfile,"C:/Users/17126…

如何定制化跑腿小程序源码

跑腿小程序源码为您提供了一个强大的起点&#xff0c;但要创建一个成功的本地服务平台&#xff0c;您通常需要对源码进行定制化。这篇文章将介绍如何定制化跑腿小程序源码&#xff0c;包括添加新功能、修改界面和优化用户体验。 选择合适的跑腿小程序源码 首先&#xff0c;您…

Linux查看端口号及进程信息

Linux查看端口号及进程 Linux查看端口号 netstat netstat -tuln显示当前正在监听的端口号以及相关的进程信息 ss ss -tuln与netstat类似&#xff0c;ss也可以用于显示当前监听的端口以及相关信息 isof isof -i :端口号端口号替换为具体要查找的端口号&#xff0c;显示该端…

Leetcode 75——1768.交替合并字符串 解题思路与具体代码【C++】

一、题目描述与要求 1768. 交替合并字符串 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你两个字符串 word1 和 word2 。请你从 word1 开始&#xff0c;通过交替添加字母来合并字符串。如果一个字符串比另一个字符串长&#xff0c;就将多出来的字母追加到合并后字符…

DOSBox和MASM汇编开发环境搭建

DOSBox和MASM汇编开发环境搭建 1 安装DOSBox2 安装MASM3 编译测试代码4 运行测试代码5 调试测试代码 本文属于《 X86指令基础系列教程》之一&#xff0c;欢迎查看其它文章。 1 安装DOSBox 下载DOSBox和MASM&#xff1a;https://download.csdn.net/download/u011832525/884180…

MySQL操作合集

数据库的操作 创建数据库 create database [if not exists] db_name [character set utf8] [collate utf8_general_ci];查看所有数据库 show databases;查看数据库的创建语句 show create database db_name;修改数据库 alter database db_name character set utf8 colla…

Linux之open/close/read/write/lseek记录

一、文件权限 这里不做过多描述&#xff0c;只是简单的记录&#xff0c;因为下面的命令会涉及到。linux下一切皆是文件包括文本、硬件设备、管道、数据库、socket等。通过ls -l 命令可以查看到以下信息 drwxrwxrwx 1 root root 0 Oct 10 17:06 open -rwxrwxrwx 1 root roo…

内网渗透——隧道代理

文章目录 代理代理使用场景VPS建立隧道frpMSF木马生成监听开启frp服务端和客户端执行exe木马文件 代理 实验环境&#xff1a; 攻击机kali&#xff1a;192.168.188.133&#xff08;NAT模式&#xff09; 模拟的公网服务器&#xff08;本机&#xff09;&#xff1a;10.9.75.239 …

【数据库——MySQL(实战项目1)】(4)图书借阅系统——触发器

目录 1. 简述2. 功能代码2.1 创建两个触发器&#xff0c;分别在借出或归还图书时&#xff0c;修改借阅人表中的已借数目(附加&#xff1a;借阅人表的总借书数、图书表的借阅次数以及更新图书表的图书状态为(已借出/在架上))字段&#xff1b;2.2 创建触发器&#xff0c;当借阅者…

相似性搜索:第 3 部分--混合倒排文件索引和产品量化

接续前文&#xff1a;相似性搜索&#xff1a;第 2 部分&#xff1a;产品量化 SImilarity 搜索是一个问题&#xff0c;给定一个查询的目标是在所有数据库文档中找到与其最相似的文档。 一、介绍 在数据科学中&#xff0c;相似性搜索经常出现在NLP领域&#xff0c;搜索引擎或推…

Codeforces Round 903 (Div. 3)

D. Divide and Equalize Example input Copy 7 5 100 2 50 10 1 3 1 1 1 4 8 2 4 2 4 30 50 27 20 2 75 40 2 4 4 3 2 3 1 output Copy YES YES NO YES NO YES NONote The first test case is explained in the problem statement. 很重要很重要的知识点&a…

Windows端口号被占用的查看方法及解决办法

Windows端口号被占用的查看方法及解决办法 Error starting ApplicationContext. To display the conditions report re-run your application with debug enabled. 2023-10-14 22:58:32.069 ERROR 6488 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter : ***…

CustomNavBar 自定义导航栏视图

1. 创建偏好设置键 CustomNavBarTitlePreferenceKey.swift import Foundation import SwiftUI//State private var showBackButton: Bool true //State private var title: String "Title" //"" //State private var subtitle: String? "SubTitl…