【数据结构与算法】详解计数排序:小范围整数排序的最佳选择

            💓 博客主页:倔强的石头的CSDN主页 

           📝Gitee主页:倔强的石头的gitee主页

            ⏩ 文章专栏:《数据结构与算法》

                                  期待您的关注

 

1b7335aca73b41609b7f05d1d366f476.gif

 

目录

 一、引言

二、计数排序的基本原理

三、实现步骤

1. 确定数据范围

2. 初始化计数数组

3. 统计元素频率

4. 排序

5. 清理资源

四、C语言实现代码

🍃计数排序升序代码

🍃计数排序降序代码 

五、性能分析

🍃时间复杂度:O(n + k),近似于O(n)

🍃空间复杂度:O(k)

🍃稳定性:稳定

六、计数排序优缺点分析

优点

缺点

总结

 


 

 一、引言

传统的比较型排序算法(如快速排序、归并排序等)虽然应用广泛,但在面对特定类型的数据时,其性能往往受到一定限制。这时,非比较型排序算法,如计数排序,便展现出了其独特的优势。

 

本文旨在深入剖析计数排序的奥秘与魅力,从基本原理到实现步骤,从优缺点分析到实际应用场景,全面展现这一排序算法的独特之处。通过本文的阅读,读者将能够深刻理解计数排序的工作原理,掌握其实现方法,并学会在合适的场景下灵活运用这一算法,以提升数据处理的效率和质量。

 

二、计数排序的基本原理

计数排序(Counting Sort)是一种非比较型排序算法,它的基本原理是通过统计每个元素的出现次数,然后利用这些信息来重建排序后的数组。

 

核心思想在于:通过统计每个元素在数组中出现的次数,来确定该元素在排序后数组中的位置。这种方法在处理具有明显范围限制且分布相对均匀的整数数据时,尤为高效。

作为一种线性时间复杂度的排序,它要求输入的数据必须是有确定范围的整数。

 请看下图动图演示

027b5605bab4416094da68800bedf0d6.gif

 

三、实现步骤

1. 确定数据范围

首先,代码通过遍历待排序数组 a,找出其中的最大值 max 和最小值 min。这两个值用于确定计数数组 count 的大小,因为计数数组需要覆盖待排序数组中所有可能出现的值(在最小值和最大值之间)。

int min = INT_MAX;
int max = INT_MIN;
for (int i = 0; i < n; i++)//求得最大值和最小值
{if (a[i] > max)max = a[i];if (a[i] < min)min = a[i];
}

 

2. 初始化计数数组

根据最大值和最小值计算出的范围(max - min + 1),代码使用 calloc 分配了一个足够大的整数数组 count,并将所有元素初始化为 0。这个数组用于统计待排序数组中每个值出现的次数。使用 calloc 而不是 malloc 加初始化是为了确保所有元素都初始化为 0,因为计数排序需要这些初始值来正确统计。

int size = max - min + 1;
int* count = (int*)calloc(size, sizeof(int));//根据数据范围申请空间
if (count == NULL)
{perror("calloc fail\n");return;
}

 

3. 统计元素频率

接下来,代码再次遍历待排序数组 a,这次是为了统计每个元素出现的次数。对于数组 a 中的每个元素 a[i],代码通过 count[a[i] - min]++ 将 a[i] 的出现次数记录在 count 数组的相应位置上。这里减去 min 是为了将 a 中的值映射到 count 数组的有效索引范围内。

for (int i = 0; i < n; i++)//遍历原数组,将数据出现的次数写入count数组对应的位置
{count[a[i] - min]++;//核心代码1
}

 

4. 排序

代码遍历 count 数组,并根据每个值出现的次数,将对应的值依次放回原数组 a 中。这里使用了一个双层循环,外层循环遍历 count 数组的每个索引(即待排序数组中的每个可能值),内层循环(通过 while 循环实现)则根据 count[j] 的值(即该值出现的次数)将 j + min(即原始值)放回原数组 a 中。每次放回一个值后,count[j] 递减,直到该值的所有出现都被放回原数组。

int i = 0;
for (int j = 0; j < size; j++)//遍历count数组,根据数据出现次数,将数据写入原数组对应的位置
{while (count[j]--)//每写入一次,次数--{a[i++] = j + min;//核心代码2}
}

 

5. 清理资源

最后,代码释放了 count 数组占用的内存,并将其指针设置为 NULL,以避免野指针问题。

free(count);
count = NULL;

 

四、C语言实现代码

🍃计数排序升序代码

void CountSort1(int* a, int n)//计数排序升序
{int min = INT_MAX;int max = INT_MIN;for (int i = 0; i < n; i++)//求得最大值和最小值{if (a[i] > max)max = a[i];if (a[i] < min)min = a[i];}int size = max - min + 1;int* count = (int*)calloc(size, sizeof(int));//根据数据范围申请空间if (count == NULL){perror("calloc fail\n");return;}for (int i = 0; i < n; i++)//遍历原数组,将数据出现的次数写入count数组对应的位置{count[a[i] - min]++;//核心代码1}int i = 0;for (int j = 0; j < size; j++)//遍历count数组,根据数据出现次数,将数据写入原数组对应的位置{while (count[j]--)//每写入一次,次数--{a[i++] = j + min;//核心代码2}}free(count);count = NULL;
}

🍃计数排序降序代码 

void CountSort2(int* a, int n)//计数排序降序
{int min = INT_MAX;int max = INT_MIN;for (int i = 0; i < n; i++)//求得最大值和最小值{if (a[i] > max)max = a[i];if (a[i] < min)min = a[i];}int size = max - min + 1;int* count = (int*)calloc(size, sizeof(int));//根据数据范围申请空间if (count == NULL){perror("calloc fail\n");return;}for (int i = 0; i < n; i++)//遍历原数组,将数据出现的次数写入count数组对应的位置{count[a[i] - min]++;//核心代码1}int i = 0;for (int j = size-1; j >=0; j--)//遍历count数组,根据数据出现次数,将数据写入原数组对应的位置{while (count[j]--)//每写入一次,次数--{a[i++] = j + min;//核心代码2}}free(count);count = NULL;
}

 

 

五、性能分析

🍃时间复杂度:O(n + k),近似于O(n)

计数排序的时间复杂度主要由以下几个部分组成:

  1. 确定数据范围:遍历一次待排序数组,找出最大值和最小值,这个过程的时间复杂度是O(n),其中n是数组的长度。

  2. 初始化计数数组:根据最大值和最小值确定计数数组的大小,并初始化所有元素为0。这一步的时间复杂度主要取决于计数数组的大小,但因为是常数时间操作(尽管这个“常数”可能很大,但它不随n的变化而变化),所以通常认为它是O(1)的,但更准确地说,它是O(k),其中k是数据范围的大小(即最大值和最小值之间的差值加1)。然而,在分析计数排序的时间复杂度时,我们更关注n,因此这一步通常被忽略或视为O(1)。

  3. 统计元素频率:再次遍历待排序数组,统计每个元素的出现次数,并将结果存储在计数数组中。这一步的时间复杂度是O(n)。

  4. 累加计数(隐含步骤):在计数排序的某些实现中,这一步是显式的,但在你的代码中,它是隐含的,因为你在填充原数组时直接使用了计数数组的信息。无论是否显式进行,这一步的时间复杂度都是O(k)。然而,由于我们更关注n,且k通常远小于n(对于实际应用中的许多情况),这一步的时间复杂度通常被忽略。

  5. 排序:遍历计数数组,根据元素的出现次数将元素放回原数组。这一步的时间复杂度是O(n + k),因为你需要遍历计数数组(O(k))并将元素放回原数组(O(n))。但是,由于k通常远小于n,且这一步是计数排序中唯一与n直接相关的步骤(除了确定数据范围),所以这一步的时间复杂度通常简化为O(n)。

综上所述,计数排序的总时间复杂度是O(n + k)。然而,在实际应用中,由于k通常远小于n(例如,当排序的是一定范围内的整数时),计数排序的性能可以近似地看作是线性的,即O(n)。

 

🍃空间复杂度:O(k)

计数排序的空间复杂度主要取决于计数数组的大小,即O(k),其中k是数据范围的大小。如果k与n相比很大,那么计数排序可能会消耗大量的内存。因此,计数排序只适用于数据范围不是很大的情况。

 

🍃稳定性:稳定

计数排序能够保持相等元素的相对顺序不变,即它是稳定的排序算法。

计数排序通过统计每个元素的出现次数,并根据这些统计信息来重建排序后的数组。在这个过程中,如果两个元素的值相等,它们会被放入计数数组的同一个位置(或者更准确地说,是相邻的位置,因为计数数组会记录每个值出现的次数),并且在重建排序后的数组时,这些相等的元素会按照它们在原数组中的顺序被依次放回。

六、计数排序优缺点分析

优点

  1. 稳定性:计数排序是稳定的排序算法。如果两个元素相等,它们在排序后的数组中的相对位置与排序前相同。

  2. 高效性:在数据范围不是很大的情况下,计数排序的时间复杂度可以认为是线性的,即O(n+k),其中n是数组的长度,k是数据范围的大小。这使得计数排序在处理具有明确范围且分布相对均匀的整数数据时非常高效。

  3. 易于实现:计数排序的实现相对简单直观,不需要复杂的比较和交换操作。

  4. 适用场景广泛:计数排序不仅适用于整数排序,还可以扩展到其他类型的数据排序,只要能够确定数据的范围并且数据分布相对均匀即可。

缺点

  1. 空间复杂度高:计数排序需要额外的空间来存储计数数组,这个数组的大小取决于数据的范围。如果数据的范围很大,那么计数数组将占用大量的内存空间,可能导致内存溢出。

  2. 数据范围限制:计数排序要求能够确定数据的范围,这限制了它的应用场景。如果数据的范围很大或者无法确定,那么计数排序可能不是一个好的选择。

  3. 对重复数据敏感:当数据中存在大量重复元素时,计数排序的效率会受到影响,因为计数数组中的某些位置会被多次更新。然而,这通常不会显著影响总体性能,因为排序过程仍然是线性的。

  4. 非原地排序:计数排序不是原地排序算法,因为它需要额外的空间来存储计数数组。这可能会在某些内存受限的环境下成为问题。

总结

计数排序是一种高效的排序算法,特别适用于一定范围内的整数排序。它的稳定性和高效性使得它在处理特定类型的数据时非常有用。然而,计数排序的空间复杂度较高,且对数据范围有一定的限制,这限制了它的应用范围。在选择排序算法时,需要根据具体的应用场景和数据特性来决定是否使用计数排序。如果数据范围明确且分布相对均匀,且内存空间足够,那么计数排序是一个很好的选择。

 

 

 

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

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

相关文章

Serverless Knative冷启动与自动扩缩容研究:从原理到实践

最近一个研究生网页的提问&#xff0c;然后就有了这篇博客&#xff01; 大佬你好&#xff0c;我看到您的关于Serverless的文章于是十分冒昧的向您提问。我现在是一名在研究通过Serverless容器调度解决冷启动问题的本科生&#xff0c;导师放养&#xff0c;就让看论文但是后面的代…

ubuntu20.04.6 安装Skywalking 10.0.1

1.前置准备 1.1. **jdk17&#xff08;Skywalking10 jdk22不兼容&#xff0c;用17版本即可&#xff09;**安装&#xff1a; https://blog.csdn.net/CsethCRM/article/details/140768670 1.2. elasticsearch安装&#xff1a; https://blog.csdn.net/CsethCRM/article/details…

Python入门宝藏《看漫画学Python》,495页漫画带你弄清python知识点!简单易懂 | 附PDF全彩版

华为出品的《看漫画学Python》全彩PDF教程是一本适合Python初学者的学习资料&#xff0c;通过漫画的形式将复杂的Python技术问题简单化&#xff0c;使学习过程更加生动有趣。以下是对该教程的内容简介、本书概要及本书目录的详细解析&#xff1a; 内容简介 《看漫画学Python》…

手机三要素接口怎么对接呢?(一)

一、什么是手机三要素&#xff1f; 手机三要素又叫运营商三要素&#xff0c;运营商实名认证&#xff0c;运营商实名核验&#xff0c;手机三要素实名验证&#xff0c;手机三要素实名核验&#xff0c;每个人的称呼都不同&#xff0c;但是入参和出参是一样的。 输入姓名、身份证…

MATLAB基础:函数与函数控制语句

今天我们继续学习Matlab中函数相关知识。 API的查询和调用 help 命令是最基本的查询方法&#xff0c;可查询所有目录、指定目录、命令、函数。 我们直接点击帮助菜单即可查询所需的API函数。 lookfor 关键字用于搜索相关的命令和函数。 如&#xff0c;我们输入lookfor inpu…

矩估计与最大似然估计的通俗理解

点估计与区间估计 矩估计与最大似然估计都属于点估计&#xff0c;也就是估计出来的结果是一个具体的值。对比区间估计&#xff0c;通过样本得出的估计值是一个范围区间。例如估计馒头店每天卖出的馒头个数&#xff0c;点估计就是最终直接估计每天卖出10个&#xff0c;而区间估…

【机器学习基础】机器学习的数学基础

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈Python机器学习 ⌋ ⌋ ⌋ 机器学习是一门人工智能的分支学科&#xff0c;通过算法和模型让计算机从数据中学习&#xff0c;进行模型训练和优化&#xff0c;做出预测、分类和决策支持。Python成为机器学习的首选语言&#xff0c;…

鸿蒙(HarmonyOS)DatePicker+TimePicker时间选择控件

一、操作环境 操作系统: Windows 11 专业版、IDE:DevEco Studio 3.1.1 Release、SDK:HarmonyOS 3.1.0&#xff08;API 9&#xff09; 二、效果图 可实现两种选择方式&#xff0c;可带时分选择&#xff0c;也可不带&#xff0c;使用更加方便。 三、代码 SelectedDateDialog…

2024下半年,前端的技术风口来了

“ 你近期有体验过哪些大模型产品呢&#xff1f; 你有使用大模型API做过一些实际开发吗&#xff1f; 在你日常开发中&#xff0c;可以与大模型相关应用结合来完成工作吗&#xff1f; ” **最近&#xff0c;一直在和同事聊&#xff0c;关于前端可以用大模型干点啥&#xff…

实战:安装ElasticSearch 和常用操作命令

概叙 科普文&#xff1a;深入理解ElasticSearch体系结构-CSDN博客 Elasticsearch各版本比较 ElasticSearch 单点安装 1 创建普通用户 #1 创建普通用户名&#xff0c;密码 [roothlink1 lyz]# useradd lyz [roothlink1 lyz]# passwd lyz#2 然后 关闭xshell 重新登录 ip 地址…

Nat Med·UNI:开启计算病理学新篇章的自监督基础模型|顶刊精析·24-07-31

小罗碎碎念 本期推文主题 这一期推文是病理AI基础模型UNI的详细介绍&#xff0c;原文如下。下期推文会介绍如何使用这个模型&#xff0c;为了你能看懂下期的推文&#xff0c;强烈建议你好好看看今天这期推文。 看完这篇推文以后&#xff0c;你大概就能清楚这个模型对自己的数据…

卷积神经网络(六)---实现 cifar10 分类

cifar10 数据集有60000张图片&#xff0c;每张图片的大小都是 32x32 的三通道的彩色图&#xff0c;一共是10种类别、每种类别有6000张图片&#xff0c;如图4.27所示。 图 4.27 cifar数据集 使用前面讲过的残差结构来处理 cifar10 数据集&#xff0c;可以实现比较高的准确率。 …

麦田物语第十五天

系列文章目录 麦田物语第十五天 文章目录 系列文章目录一、构建游戏的时间系统二、时间系统 UI 制作总结 一、构建游戏的时间系统 在该游戏中我们要构建年月日天时分秒等时间的概念&#xff0c;从而实现季节的更替&#xff0c;昼夜的更替等&#xff08;不同的季节可以播种不同…

【MATLAB源码】机器视觉与图像识别技术实战示例文档---鱼苗面积预测计数

系列文章目录 第一篇文章&#xff1a;【MATLAB源码】机器视觉与图像识别技术—视觉系统的构成(视频与图像格式转换代码及软件下载) 第二篇文章&#xff1a;【MATLAB源码】机器视觉与图像识别技术(2)—图像分割基础 第三篇文章&#xff1a;【MATLAB源码】机器视觉与图像识别技术…

提交高通量测序处理数据到 GEO --- 操作流程

❝ 写在前面 由于最近在提交课题数据到 NCBI 数据库&#xff0c;整理了相关笔记。本着自己学习、分享他人的态度&#xff0c;分享学习笔记&#xff0c;希望能对大家有所帮助。推荐先按顺序阅读往期内容&#xff1a; 1. 提交高通量测序数据到 GEO --- 说明书 2. 提交高通量测序原…

jQuery前端网页制作

1、Jquery的概述 1.1JavaScript库 JavaScript 高级程序设计(特别是对浏览器差异的复杂处理),通常很困难也很耗时。 为了应对这些调整,许多的 JavaScript (helper) 库应运而生。 这些 JavaScript 库常被称为 JavaScript 框架。 市面上一些广受欢迎的 JavaScript 框架:…

基于Docker搭建ELK

目录 1.系统操作 2.搭建es 3.kibana(新起终端跟es一起启动) 4.logstash&#xff08;新起终端和es一起启动&#xff09; 5.修改logstash配置文件 6. 创建索引 7. exit #退出容器 8. 在logstash节点插入数据&#xff0c;测试是否能拿取到&#xff08;下面如果本身有数据…

基于多种机器学习的豆瓣电影评分预测与多维度可视化【可加系统】

有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主 在本研究中&#xff0c;我们采用Python编程语言&#xff0c;利用爬虫技术实时获取豆瓣电影最新数据。通过分析豆瓣网站的结构&#xff0c;我们设计了一套有效的策略来爬取电影相关的JSON格式数据。…

[FBCTF2019]RCEService (PCRE回溯绕过和%a0换行绕过)

json格式输入ls出现index.php 这道题原本是给了源码的&#xff0c;BUUCTF没给 源码&#xff1a; <?phpputenv(PATH/home/rceservice/jail);if (isset($_REQUEST[cmd])) {$json $_REQUEST[cmd];if (!is_string($json)) {echo Hacking attempt detected<br/><br/…

ElasticSearch学习篇15_《检索技术核心20讲》进阶篇之TopK检索

背景 学习极客实践课程《检索技术核心20讲》https://time.geekbang.org/column/article/215243&#xff0c;文档形式记录笔记。 相关问题&#xff1a; ES全文检索是如何进行相关性打分的&#xff1f;ES中计算相关性得分的时机?如何加速TopK检索&#xff1f;三种思路 精准To…