时间复杂度为 O(n) 的排序算法

大家好,我是 方圆。本文介绍线性排序,即时间复杂度为 O(n) 的排序算法,包括桶排序,计数排序和基数排序,它们都不是基于比较的排序算法,大家重点关注一下这些算法的适用场景。

桶排序

桶排序是分治策略的一个典型应用。它通过设置一些具有大小顺序的桶,每个桶对应一个数据范围,将数据平均分配到各个桶中;然后,在每个桶内部分别执行排序;最终按照桶的顺序将所有数据依次取出合并,组成的序列就是有序的了,如下图所示:

1.jpeg

桶排序的算法流程我将其分成三个步骤:

  • 初始化桶:以范围为 0 - 49 的数据为例,分为 5 个桶

  • 分桶:将要排序数组中的元素加入桶中

  • 出桶:该步骤需要在桶中完成排序后,依次出桶合并

    /*** 桶排序:指定数据范围为0 - 49,分桶为5个,每10个数为一个桶*/public void sort(int[] nums) {// 声明5个桶List<ArrayList<Integer>> buckets = new ArrayList<>();for (int i = 0; i < 5; i++) {buckets.add(new ArrayList<>());}// 数组元素分桶intoBucket(buckets, nums);// 出桶outOfBucket(buckets, nums);}/*** 分桶*/private void intoBucket(List<ArrayList<Integer>> buckets, int[] nums) {for (int num : nums) {int bucketIndex = num / 10;buckets.get(bucketIndex).add(num);}}/*** 出桶*/private void outOfBucket(List<ArrayList<Integer>> buckets, int[] nums) {// 出桶覆盖原数组值int numsIndex = 0;for (ArrayList<Integer> bucket : buckets) {// 先排序 再出桶bucket.sort(Comparator.comparingInt(x -> x));for (Integer num : bucket) {nums[numsIndex++] = num;}}}

算法特性:

  • 空间复杂度:O(n + k)

  • 自适应排序:与桶划分情况和桶内使用的排序算法有关

  • 稳定排序/非稳定排序:与桶内使用的排序算法有关

  • 非原地排序

桶排序比较适用于 外部排序,所谓的外部排序就是数据存储在外部磁盘中,数据量很大,但是内存又有限,无法将所有数据全部加载进来,比如有 1G 的数据需要排序,但是内存只有几百MB的情况。我们可以根据数据范围将其划分到 N 个桶中,划分完成后每个桶的大小不超过可用内存大小,对每个桶内的数据进行排序,排序完成后生成 N 个小文件,最后我们再将这 N 个小文件写入到一个大文件中即可。如果数据在某些范围内并不是均匀分布的话,有些范围内的数据特别多,那么这就需要我们再对其划分成更细粒度的桶,直到满足内存的使用要求,但是这样我们的桶就不是按照范围均匀划分的了。

计数排序

计数排序是桶排序的一种特殊情况,只是它定义的“桶”的粒度更细,每个桶中只包含一个单位范围的数字,那么每个“桶”内的数值都是相等的。它适合数据范围不大,但数据量很大的排序场景,比如高考考生成绩排名,86 万考生,满分 750 分,需要划分 751 个桶,将这些考生的成绩划分到各个桶中后,依次取出即可。

看到这里你可能会觉得这不就是桶排序吗?计数排序的计数体现在哪里呢?别急,我们看下下面这个排序的例子,简单起见,假设有 8 个考生,他们的分数为 [2, 5, 3, 0, 2, 3, 0, 3],分数范围为 0 ~ 5,那么我们需要创建 6 个桶,规定桶中保存的不是对应的元素,而是对应分数元素出现的数量,并根据分数将桶中的计数值累加,如下图所示:

2.jpeg

我们先看分数 0 的桶,它是该数据范围内最小的分数,它的计数为 2,根据计数值我们可以确定分数为 0 的两个元素占用该数据范围的前两个索引位置,所以计数表示的是对应数值的索引位置。我们再看看其他的桶来验证一下:可以发现分数 2 的桶计数也为 2,但是前两个索引位置已经被分数 0 占用了呀,分数 2 的计数应该是 4 才对,所以,我们还需要一步操作:叠加前面分数出现的次数,这样分数 2 的计数值便为 4,可以发现计数值其实表示的是某数字占用的第 N 个索引,如果我们想知道其中分数 2 的索引位置,将计数值 4 进行减 1 即可,即它的索引值为 3,而且每取完某数字一次,需要将该计数值减 1。排序流程如下:

计数排序.png

这样一步步操作完成之后,最终数组是有序的。计数排序的代码如下:

    /*** 计数排序的计数体现在小于等于某个数出现的次数 - 1 即为该数在原数组排序后的位置*/public void sort(int[] nums) {if (nums.length <= 1) {return;}// 寻找数组中的最大值来以此定义max + 1个桶int max = Arrays.stream(nums).max().getAsInt();// 定义桶,索引范围即数组值的最大范围,每个桶中保存的是该数字出现的次数,计数排序的计数概念出现int[] bucket = new int[max + 1];// 计算每个数的个数在桶中累加Arrays.stream(bucket).forEach(x -> bucket[x]++);// 依次累加桶中的数,该数表示小于等于该索引值的数量for (int i = 1; i < bucket.length; i++) {bucket[i] += bucket[i - 1];}// 创建临时数组来保存排序结果值int[] res = new int[nums.length];// 倒序遍历原数组,不改变相同元素的相对顺序for (int i = nums.length - 1; i >= 0; i--) {// 根据桶中的 计数 找出该数的索引int index = bucket[nums[i]] - 1;// 根据索引在结果数组中赋值res[index] = nums[i];// 该数分配完成后,需要将桶中的计数-1bucket[nums[i]]--;}// 结果数组覆盖原数组System.arraycopy(res, 0, nums, 0, res.length);}

基数排序

基数排序对待排序数据是有特殊要求的,需要数据可以分割出独立的“位”,并且位与位之间要有递进关系,根据递进关系对每一位进行排序,获得最终排序结果。

我们先来看一下使用基数排序处理 [3, 4, 100, 11, 33] 数组的过程:

基数排序.png

因为整数每位取数范围为 0 ~ 9,所以创建了 10 个桶,这 10 个桶已经有了从大到小的顺序。排序时从整数的个位开始,到最高位终止,排序的轮次为最大整数的位数,在每轮排序中,根据当前位数值大小入桶,完成后再按顺序出桶,最终结果即为排序结果。代码如下:

    private void sort(int[] nums) {if (nums.length <= 1) {return;}// 1. 整数的每位取值范围为 0-9,因此需要创建10个桶Queue<Integer>[] buckets = createBuckets();// 2. 获取基数排序的执行轮次int radixRounds = getRadixRounds(nums);// 3. 根据执行轮次处理各个"位",eg: 第一轮处理个位...for (int round = 1; round <= radixRounds; round++) {for (int num : nums) {// 获取所在桶的索引int bucketIndex = getBucketIndex(num, round);// 进桶buckets[bucketIndex].offer(num);}// 出桶赋值,当前结果为根据当前位排序的结果int numsIndex = 0;for (Queue<Integer> bucket : buckets) {while (!bucket.isEmpty()) {nums[numsIndex++] = bucket.poll();}}}}/*** 创建大小为10的数组作为桶,每个桶都是一个队列*/@SuppressWarnings("unchecked")private Queue<Integer>[] createBuckets() {Queue<Integer>[] buckets = new Queue[10];for (int i = 0; i < buckets.length; i++) {buckets[i] = new LinkedList<>();}return buckets;}/*** 获取基数排序的执行轮次*/private int getRadixRounds(int[] nums) {return String.valueOf(Arrays.stream(nums).max().getAsInt()).length();}/*** 获取该数所在桶的索引*/private int getBucketIndex(int num, int round) {int bucketIndex = 0;while (round != 0) {bucketIndex = num % 10;num /= 10;round--;}return bucketIndex;}

基数排序比较适用于数据范围比较大且位数相对均匀的数据排序,比如排序手机号或者学号,它的时间复杂度接近于 O(n)。


巨人的肩膀

  • 《数据结构与算法之美》:第 3.6 章 线性排序:如何根据年龄给 100 万个用户排序

  • 《Hello 算法》:第 11.8、11.9 和 11.10 章

  • 《算法导论》:第 8 章 线性时间排序

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

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

相关文章

kingbase控制文件重建

背景&#xff1a; 测试版本&#xff1a;V008R006C005B0023 sys_control文件在数据目录的global下 若sys_control文件损坏或被删&#xff0c;会导致库无法启动 处理方式&#xff1a; 所在目录为数据目录 1、尝试dryrun touch global/sys_control sys_resetwal -l 00000001…

vue基本理解

1、js闭包&#xff0c;作用&#xff1f;&#xff1f; 闭包是指在一个函数内部&#xff0c;可以访问外部函数的变量&#xff0c;即使外部函数已经执行完毕。闭包的作用有&#xff1a; 保护变量&#xff1a;闭包可以保护函数内部的变量&#xff0c;使其不受外部环境的影响。实现…

100 个 NLP 面试问题

100 个 NLP 面试问题 一、 说明 对于技术磨练中&#xff0c;其中一项很酷的技能培训是提问。不知道答案并没有多大的错;错就错在不谷歌这些疑问。本篇就是在面试之前&#xff0c;您将此文档复制给自己&#xff0c;做一个系统的模拟实战。 二、经典NLP问题&#xff08;共8题&a…

代码随想录算法训练营第三十四天|62.不同路径 , 63. 不同路径 II

确定dp数组&#xff08;dp table&#xff09;以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 62.不同路径 代码随想录 视频讲解&#xff1a;动态规划中如何初始化很重要&#xff01;| LeetCode&#xff1a;62.不同路径_哔哩哔哩_bilibili class Solut…

洪水攻击:理解与防御

一、引言 洪水攻击是一种常见的网络攻击手段&#xff0c;其特点是攻击流量巨大&#xff0c;远远超过目标系统或网络的正常处理能力。这种攻击方式会给目标系统带来巨大的压力&#xff0c;可能导致系统崩溃或网络瘫痪。本文将深入探讨洪水攻击的本质、常见类型以及防御策略。 …

gogs 搭建私人git服务器遇到的问题汇总

1、新建用户 运行 sudo adduser git 新建好 git 用户。 su git 以 git 用户登录 2、下载解压安装包 下载地址&#xff1a;From binary - Gogs 选择相对应的版本&#xff0c;下载并解压到/home/git文件下 3、运行安装 首先建立好数据库。在 Gogs 目录的 scripts/mysql.sql…

【ETOJ P1014】straax‘aks Array 题解(多重循环+暴力枚举+位运算)

题目描述 给定一个长度为 n n n 的数组 a a a 和一个整数 m m m&#xff0c;问数组中有多少个三元组 ( i , j , k ) (i,j,k) (i,j,k)&#xff0c;满足&#xff1a; i < j < k i < j < k i<j<k ( a i a j a k ) ( a i ⊕ a j ⊕ a k ) ≥ m (a_i a_j…

Jenkins任意文件读取漏洞(CVE-2024-23897)复现

Jenkins 有一个内置的命令行界面CLI&#xff0c;在处理 CLI 命令时Jenkins 使用args4j 库解析 Jenkins 控制器上的命令参数和选项。此命令解析器具有一个功能&#xff0c;可以将参数中后跟文件路径的字符替换为文件内容 ( expandAtFiles)。具有Overall/Read权限的攻击者可以读取…

mysql主流版本5.5/5.6/5.7/8.0重置修改密码方法

最近几天来回切换各个Mysql版本重置密码&#xff0c;记录一下各个版本重置密码的方法。 MySql 5.5 SET PASSWORD FOR usernamelocalhost PASSWORD(new_password);MySql5.6 SET PASSWORD FOR usernamelocalhost new_password;MySql5.7 ALTER USER usernamelocalhost IDENT…

PeakCAN连接到WSL2 Debian

操作步骤 按照以下步骤进行操作&#xff1a; 在Windows下安装PeakCAN驱动并安装&#xff0c;地址是https://www.peak-system.com/PCAN-USB.199.0.html?&L1 在Windows下安装usbipd&#xff0c;地址是https://github.com/dorssel/usbipd-win/releases&#xff0c;最新版是…

C#字符串匹配(模式搜索)BF(Brute Force)暴力算法

暴力算法概念 暴力算法&#xff08;Brute Force Algorithm&#xff09;是一种简单直接的解决问题的方法&#xff0c;它通过穷举所有可能的解决方案来寻找问题的解。暴力算法通常是基于穷举搜索的思想&#xff0c;遍历所有可能的情况&#xff0c;然后逐一检查每种情况是否满足问…

小米服务治理——客户端熔断器(Google SRE客户端熔断器)

目录 前言 一、什么是Google SRE熔断器 二、Google SRE 熔断器的工作流程&#xff1a; 三、客户端熔断器 (google SRE 熔断器) golang GRPC 实现 四、客户端熔断器 (google SRE 熔断器) golang GRPC单元测试 大家可以关注个人博客&#xff1a;xingxing – Web Developer …

nest.js实现登录验证码功能(学习笔记)

安装express-session npm i express-session 引入 注册session import * as session from express-session;import { NestFactory } from nestjs/core; import {DocumentBuilder,SwaggerModule, } from nestjs/swagger;import { AppModule } from ./app.module;async functio…

解决iCloud备份显灰问题的完全指南

目录 ​编辑 引言 问题背景 可能的原因 1 网络连接问题 2 ICloud账户异常 3 存储空间不足 4 备份设置问题 5 iOS版本问题 解决方法 3.1 检查网络连接 3.2 检查ICloud账户 3.3 检查存储空间 3.4 检查备份设置 3.5 更新iOS版本 3.6 重启设备 3.7 重置ICloud设置 …

CSC联合培养博士申请亲历|联系外导的详细过程

在CSC申报的各环节中&#xff0c;联系外导获得邀请函是关键步骤。这位联培博士同学的这篇文章&#xff0c;非常详细且真实地记录了申请过程、心理感受&#xff0c;并提出有益的建议&#xff0c;小编特推荐给大家参考。 2024年国家留学基金委公派留学项目即将开始&#xff0c;其…

简单说说redis分布式锁

什么是分布式锁 分布式锁&#xff08;多服务共享锁&#xff09;在分布式的部署环境下&#xff0c;通过锁机制来让多客户端互斥的对共享资源进行访问/操作。 为什么需要分布式锁 在单体应用服务里&#xff0c;不同的客户端操作同一个资源&#xff0c;我们可以通过操作系统提供…

互联网摸鱼日报(2024-02-02)

互联网摸鱼日报(2024-02-02) 博客园新闻 马斯克&#xff1a;Neuralink已探测到神经信号 Linus新年首骂&#xff1a;和谷歌大佬大吵4天&#xff0c;“你的代码就是垃圾” 从零手搓MoE大模型&#xff0c;大神级教程来了 无人出租车深圳中心区收费载客&#xff0c;硅谷同款&am…

【计网·湖科大·思科】实验六 IP数据报的发送和转发流程、默认路由和特定主机路由

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…

LLM智能体开发指南

除非你一直生活在岩石下&#xff0c;否则你一定听说过像 Auto-GPT 和 MetaGPT 这样的项目。 这些是社区为使 GPT-4 完全自治而做出的尝试。在其最原始的形式中&#xff0c;代理基本上是文本到任务。你输入一个任务描述&#xff0c;比如“给我做一个贪吃蛇游戏”&#xff0c;并使…

用 Easysearch 帮助大型车企降本增效

最近某头部汽车集团需要针对当前 ES 集群进行优化&#xff0c;背景如下&#xff1a; ES 用于支撑包括核心营销系统、管理支持系统、财务类、IT 基础设施类、研发、自动驾驶等多个重要应用&#xff0c;合计超 50 余套集群&#xff0c;累计数据超 1.5PB 。 本文针对其中一个 ES 集…