数据结构——计数、桶、基数排序

目录

引言

计数排序

1.算法思想

2.算法步骤

3.代码实现

4.复杂度分析

桶排序

1.算法思想

2.算法步骤

3.代码实现

4.复杂度分析

基数排序

1.算法思想

2.算法步骤

3.代码实现

4.复杂度分析

排序算法的稳定性

1.稳定性的概念

2.各个排序算法的稳定性

结束语


引言

目前写的排序:

数据结构——冒泡、选择、插入和希尔排序

数据结构——快速排序

数据结构——归并排序

数据结构——堆排序

有需要的可以看看。

我们接下来学习一下计数排序、桶排序、基数排序。

计数排序

计数排序(Counting Sort)是一种非基于比较的排序算法,由Harold H. Seward在1954年提出。该算法的主要优势在于其线性时间复杂度,特别适用于待排序元素为整数且范围较小的情况。

1.算法思想

计数排序的基本思想是统计数组中每个元素的出现次数,然后利用这些信息将原始序列重新组合成有序序列。它并不直接比较元素之间的大小,而是通过计算小于等于某个值的元素个数来确定该元素在排序后数组中的位置。

2.算法步骤

1.找出待排序数组中的最大值和最小值

2.创建计数数组:计数数组的长度通常设置为待排序数组中的最大值加1(如果已知范围,则可以直接设定长度为范围大小)。计数数组的初始值全部设为0。

3.统计元素出现次数:遍历待排序数组,对于数组中的每个元素,在计数数组对应下标的位置上加1,以统计该元素的出现次数。

4.修改计数数组:对计数数组进行前缀和操作,即每个位置的值变为它本身加上前一个位置的值。这样,计数数组的每个位置就代表了小于等于该下标值的元素在排序后数组中的最后位置。

5.重建输出数组:从后向前遍历待排序数组,根据计数数组中的值确定当前元素在输出数组中的位置,并更新计数数组。具体地,对于待排序数组中的每个元素,找到计数数组中对应下标的值(即该元素在排序后数组中的位置),将元素放到输出数组的相应位置,并将计数数组中对应下标的值减1。

6.输出排序后的数组:此时,输出数组就是排序后的数组。

动图演示如下:

3.代码实现

void CountSort(int* arr, int n)
{// 初始化最小值和最大值为数组的第一个元素  int min = arr[0];int max = arr[0];// 遍历数组,找到最小值和最大值  for (int i = 1; i < n; i++){if (arr[i] < min)min = arr[i]; // 更新最小值  if (arr[i] > max)max = arr[i]; // 更新最大值  }// 计算值的范围(即最大值和最小值之间的差值加1)  int range = max - min + 1;int* count = (int*)calloc(range, sizeof(int));if (count == NULL){perror("calloc fail:");return;}// 统计每个元素的出现次数  // 通过数组下标转换(arr[i] - min)将原始数组的值// 映射到计数数组的索引上  for (int i = 0; i < n; i++){count[arr[i] - min]++;}// 根据计数数组中的计数,重建排序后的数组  // 遍历计数数组,每次将对应计数值数量的元素放到arr中  int index = 0; for (int i = 0; i < range; i++){// 当count[i]大于0时,说明有i+min这个值的元素需要被放置  while (count[i]--){arr[index++] = i + min; // 放置元素,并更新index  }}free(count);count = NULL; 
}

测试一下:

4.复杂度分析

时间复杂度:由于遍历了原数组与range数组,因此时间复杂度为O(n+range)。

空间复杂度:开辟了大小为range的数组,所以空间复杂度为O(range)。

桶排序

桶排序(Bucket Sort)是一种基于分配策略的排序算法。

1.算法思想

桶排序的算法思想是将数组中的元素分配到有限数量的桶(或称为箱)里,每个桶再分别进行排序(可能使用其他排序算法或递归使用桶排序),最后合并成结果序列。

2.算法步骤

1.初始化桶:首先,需要确定桶的数量及每个桶对应的数据范围。这通常与待排序数据的范围有关,目的是使每个桶尽可能均匀地包含一部分数据。

2.分配元素:遍历待排序序列,将每个元素根据其值放入对应的桶中。这一步相当于对数据进行初步的划分和聚集。

3.桶内排序:对每个非空桶内部使用其他排序算法(如插入排序、快速排序等)进行排序,确保每个桶内的数据有序。

4.合并桶:按照桶的顺序,依次从每个桶中取出元素,合并成一个有序序列,即完成整个数据集的排序。

动图演示:

PS:这里的位数桶只是方便演示。

3.代码实现

桶内的排序我们同样可以借助插入排序来实现。

代码如下:

#define BUCKET_SIZE 10  // 定义桶的数量  
#define ARRAY_SIZE 30   // 定义数组的大小  void InsertSort(int* arr, int n)
{for (int i = 0; i < n - 1; i++){int end = i;int tmp = arr[end + 1];while (end >= 0){if (tmp < arr[end]){arr[end + 1] = arr[end];--end;}else{break;}}arr[end + 1] = tmp;}
}// 桶排序函数  
void bucketSort(int arr[], int n) 
{int bucket[BUCKET_SIZE][ARRAY_SIZE], max = arr[0], min = arr[0];int i, j, k;int b_size[BUCKET_SIZE] = { 0 }; // 每个桶的元素数量  // 找到数组中的最大值和最小值  for (i = 1; i < n; i++) {if (arr[i] > max)max = arr[i];if (arr[i] < min)min = arr[i];}// 计算桶的间隔  int range = max - min;int interval = range / BUCKET_SIZE; // 将数据分配到各个桶中  for (i = 0; i < n; i++) {int bi = (arr[i] - min) / interval; // 使用整数除法来分配桶  if (bi >= BUCKET_SIZE){bi = BUCKET_SIZE - 1;}bucket[bi][b_size[bi]++] = arr[i];}// 对每个桶进行排序  for (i = 0; i < BUCKET_SIZE; i++){InsertSort(bucket[i], b_size[i]);}// 合并桶中的数据  k = 0;for (i = 0; i < BUCKET_SIZE; i++){for (j = 0; j < b_size[i]; j++){arr[k++] = bucket[i][j];}}
}

测试一下:

4.复杂度分析

时间复杂度:

(1)如果数据均匀分布在桶中,且每个桶内能快速排序(如计数排序、插入排序等),则平均时间复杂度可以达到O(n + k),其中n是元素总数,k是桶的数量。这是因为在理想情况下,每个桶只需要处理少量元素。

(2)当输入数据已经完全有序或者极度集中在一个桶内,桶排序会退化为线性查找,此时时间复杂度为O(n^2)。

空间复杂度:由于需要额外存储每个桶以及额外的空间用于最终结果的合并,因此空间复杂度通常是O(n + k)。

基数排序

基数排序(Radix Sort)是一种非比较型整数排序算法,它通过键值的各个位的值,将要排序的元素分配到某些“桶”中,以达到排序的目的。基数排序也被称为“分配式排序”或“桶子法”(bucket sort),是桶排序的扩展。

1.算法思想

基数排序的基本原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。具体来说,它首先将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

2.算法步骤

1.计算最大数位数:首先找出待排序数组中最大数的位数。

2.统一数位长度:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。

3.按位排序:从最低位开始,依次进行一次排序。排序时,根据当前位的数值,将元素分配到对应的桶中。

4.合并桶中元素:将各个桶中的元素依次取出,合并成一个新的有序数组。

5.重复步骤3和4:对更高位进行排序,直到最高位排序完成。

3.代码实现

// 获取数字num在exp位上的数字
int Get_digit(int num, int exp) 
{return (num / exp) % 10;
}// 对数组arr中的数字按exp位进行计数排序
void CountSortDigit(int* arr, int n, int exp) 
{// 分配一个大小为10的数组来计数0-9每个数字出现的次数int* counter = (int*)malloc(sizeof(int) * 10);if (counter == NULL) {perror("malloc fail:");return;}// 初始化计数器数组memset(counter, 0, sizeof(int) * 10);// 遍历数组,计算每个数字在exp位上出现的次数for (int i = 0; i < n; i++) {int x = Get_digit(arr[i], exp);counter[x]++;  }// 累积计数,使counter[i]包含小于或等于i的数字的总数for (int i = 1; i < 10; i++) {counter[i] += counter[i - 1];}// 分配一个与原始数组相同大小的临时数组来存储排序后的结果int* ret = (int*)malloc(sizeof(int) * n);  if (ret == NULL) {perror("malloc fail:");return;}// 从后往前遍历原始数组,根据exp位上的数字将元素放入ret数组的适当位置for (int i = n - 1; i >= 0; i--) {int d = Get_digit(arr[i], exp);ret[counter[d] - 1] = arr[i];  counter[d]--;}// 将排序后的结果复制回原始数组for (int i = 0; i < n; i++) {arr[i] = ret[i];}free(ret);  free(counter);  
}void RadixSort(int* arr, int n) 
{// 找到数组中的最大值,以确定最大的位数int max = arr[0];for (int i = 1; i < n; i++) {  if (arr[i] > max) {max = arr[i];}}// 从个位开始,逐步向高位进行排序  // exp是当前的位数(1代表个位,10代表十位,依此类推)for (int exp = 1; max / exp > 0; exp *= 10) {// 对当前位数进行计数排序CountSortDigit(arr, n, exp);}
}

测试一下:

4.复杂度分析

时间复杂度:数据量为N、数据为D进制、最大位数为K。时间复杂度可以表示为O((N + D) * K)。但在实际应用中,由于D和K都相对较小,我们通常会将其简化为O(N * K),并进一步简化为O(N)。

空间复杂度:由于基数排序需要借助长度为N和D的统计数组,因此基数排序空间复杂度为O(N+D)。

排序算法的稳定性

1.稳定性的概念

排序算法的稳定性是指算法在排序过程中,对于具有相同关键字的元素,能否保持它们在原序列中的相对顺序不变。如果保持不变,则称该排序算法是稳定的;如果相对顺序可能发生变化,则称该排序算法是不稳定的。

2.各个排序算法的稳定性

排序算法平均时间复杂度最优时间复杂度最差时间复杂度空间复杂度稳定性
冒泡排序O(n^2)o(n)o(n^2)O(1)稳定
选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
插入排序O(n^2)O(n)O(n^2)O(1)稳定
希尔排序O(n^1.3)O(n)O(n^2)O(log n)不稳定
快速排序O(n log n)O(n log n)O(n log n)O(n)不稳定
归并排序O(n log n)O(n log n)O(n log n)O(1)稳定
堆排序O(n log n)O(n log n)O(n log n)O(1)不稳定
计数排序O(n+range)O(n+range)O(n+range)O(range)稳定
桶排序O(n + k)O(n + k)O(n^2)O(n + k)稳定
基数排序O(N * K)O(N * K)O(N * K)O(N + K)稳定

结束语

排序这部分大概就学到这里。

求点赞收藏评论关注!!!

感谢各位大佬的支持!!!

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

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

相关文章

C++(string类的实现)

1. 迭代器、返回capacity、返回size、判空、c_str、重载[]和clear的实现 string类的迭代器的功能就类似于一个指针&#xff0c;所以我们可以直接使用一个指针来实现迭代器&#xff0c;但如下图可见迭代器有两个&#xff0c;一个是指向的内容可以被修改&#xff0c;另一个则是指…

Pytorch最最适合研究生的入门教程,Q3 开始训练

文章目录 Pytorch最最适合研究生的入门教程Q3 开始训练3.1 训练的见解3.2 Pytorch基本训练框架work Pytorch最最适合研究生的入门教程 Q3 开始训练 3.1 训练的见解 如何理解深度学习能够完成任务&#xff1f; 考虑如下回归问题 由函数 y f ( x ) yf(x) yf(x)采样得到的100个…

【安当产品应用案例100集】018-Vmware Horizon如何通过安当ASP身份认证系统增强登录安全性

启用Radius认证是提高VMware Horizon环境安全性的有效方法&#xff0c;特别是在需要满足复杂安全要求的场景中。 启用Radius认证对于VMware Horizon具有以下几个关键优势&#xff1a; 增强安全性&#xff1a;Radius认证支持多种认证方法&#xff0c;包括PAP、CHAP、MS-CHAPv1…

web前端面试中拍摄的真实js面试题(真图)

web前端面试中拍摄的真实js面试题&#xff08;真图&#xff09; WechatIMG258.jpeg WechatIMG406.jpeg WechatIMG407.jpeg WechatIMG922.jpeg WechatIMG1063.jpeg © 著作权归作者所有,转载或内容合作请联系作者 喜欢的朋友记得点赞、收藏、关注哦&#xff01;&#xff01;…

TypeScript 算法手册 - 【冒泡排序】

文章目录 TypeScript 算法手册 - 冒泡排序1. 冒泡排序简介1.1 冒泡排序定义1.2 冒泡排序特点 2. 冒泡排序步骤过程拆解2.1 比较相邻元素2.2 交换元素2.3 重复过程 3. 冒泡排序的优化3.1 提前退出3.2 记录最后交换位置案例代码和动态图 4. 冒泡排序的优点5. 冒泡排序的缺点总结 …

【SpringBoot详细教程】-09-Redis详细教程以及SpringBoot整合Redis【持续更新】

🌲 Redis 简介 🌾 什么是Redis Redis 是C语言开发的一个开源高性能键值对的内存数据库,可以用来做数据库、缓存、消息中间件等场景,是一种NoSQL(not-only sql,非关系型数据库)的数据库 Redis是互联网技术领域使用最为广泛的存储中间件,它是「Remote DictionaryServic…

TARA分析方法论——威胁分析和风险评估方法

一、什么是TARA分析方法论 威胁分析和风险评估&#xff08;Threat Analysis and Risk Assessment&#xff09; 通过识别整车/项目的网络安全资产&#xff0c;分析其中的潜在的安全威胁&#xff0c;综合考虑威胁攻击可行性、危害影响等因素&#xff0c;识别出整车/项目可能存在…

Python并发编程(2)——初始Python多线程

左手编程&#xff0c;右手年华。大家好&#xff0c;我是一点&#xff0c;关注我&#xff0c;带你走入编程的世界。 公众号&#xff1a;一点sir&#xff0c;关注领取python编程资料 前言 什么是多线程&#xff1f; 为什么需要多线程&#xff1f; 多线程的优点和缺点&#xff1f…

前端规范工程-5:Git提交信息规范(commitlint + czg)

前面讲的都是在git提交之前的一些检查流程&#xff0c;然而我们git提交信息的时候&#xff0c;也应该是需要规范的。直接进入主题&#xff1a; 目录 需安装插件清单commitlint 介绍安装配置配置commit-msg钩子提交填写commit信息czg后续方式一&#xff1a;push触动build并上传…

Windows UAC权限详解以及因为权限不对等引发软件工具无法正常使用的实例分析

目录 ​1、什么是UAC&#xff1f; 2、微软为什么要设计UAC&#xff1f; 3、标准用户权限与管理员权限 4、程序到底以哪种权限运行&#xff1f;与哪些因素有关&#xff1f; 4.1、给程序设置以管理员权限运行的属性 4.2、当前登录用户的类型 4.3、如何通过代码判断某个进程…

2.1MyBatis——ORM对象关系映射

2.1MyBatis——ORM对象关系映射 1. 验证映射配置2.ResultType和ResultMap2.1ResultMap是最终的ORM依据2.2ResultType和ResultMap的使用区别 3.具体的转换逻辑3.1 TypeHandle类型转换 5.总结 概括的说&#xff0c;MyBatis中&#xff0c;对于映射关系的声明是由开发者在xml文件手…

手机USB连接不显示内部设备,设备管理器显示“MTP”感叹号,解决方案

进入小米驱动下载界面&#xff0c;等小米驱动下载完成后&#xff0c;解压此驱动文件压缩包。 5、小米USB驱动安装方法&#xff1a;右击“计算机”&#xff0c;从弹出的右键菜单中选择“管理”项进入。 6、在打开的“计算机管理”界面中&#xff0c;展开“设备管理器”项&…

【数据分享】2000—2023年我国省市县三级逐年植被覆盖度(FVC)数据(Shp/Excel格式)

之前我们分享过2000—2023年逐月植被覆盖度&#xff08;FVC&#xff09;栅格数据&#xff08;可查看之前的文章获悉详情&#xff09;和Excel和Shp格式的省市县三级逐月FVC数据&#xff08;可查看之前的文章获悉详情&#xff09;&#xff0c;原始的逐月栅格数据来源于高吉喜学者…

深度学习:迁移学习

目录 一、迁移学习 1.什么是迁移学习 2.迁移学习的步骤 1、选择预训练的模型和适当的层 2、冻结预训练模型的参数 3、在新数据集上训练新增加的层 4、微调预训练模型的层 5、评估和测试 二、迁移学习实例 1.导入模型 2.冻结模型参数 3.修改参数 4.创建类&#xff…

GAN|对抗| 生成器更新|判别器更新过程

如上图所示&#xff0c;生成对抗网络存在上述内容&#xff1a; 真实数据集&#xff1b;生成器&#xff1b;生成器损失函数&#xff1b;判别器&#xff1b;判别器损失函数&#xff1b;生成器、判别器更新&#xff08;生成器和判别器就是小偷和警察的关系&#xff0c;他们共用的…

kubernetes基础操作(pod生命周期)

pod生命周期 一、Pod生命周期 我们一般将pod对象从创建至终的这段时间范围称为pod的生命周期&#xff0c;它主要包含下面的过程&#xff1a; ◎pod创建过程 ◎运行初始化容器&#xff08;init container&#xff09;过程 ◎运行主容器&#xff08;main container&#xff…

记录一次病毒启动脚本

在第一次下载软件时&#xff0c;目录中配了一个使用说明&#xff0c;说是需要通过start.bat 这个文件来启动程序&#xff0c;而这个 start.bat 就是始作俑者&#xff1a; 病毒作者比较狡猾&#xff0c;其中start.bat 用记事本打开是乱码&#xff0c;但是可以通过将这个批处理…

spring揭秘24-springmvc02-5个重要组件

文章目录 【README】【1】HanderMapping-处理器映射容器【1.1】HanderMapping实现类【1.1.1】SimpleUrlHandlerMapping 【2】Controller&#xff08;二级控制器&#xff09;【2.1】AbstractController抽象控制器&#xff08;控制器基类&#xff09; 【3】ModelAndView(模型与视…

java入门基础(一篇搞懂)

​ 如果您觉得这篇文章对您有帮助的话 欢迎您分享给更多人哦 感谢大家的点赞收藏评论&#xff0c;感谢您的支持&#xff01;&#xff01;&#xff01; 首先给大家推荐比特博哥&#xff0c;java入门安装的JDk和IDEA社区版的安装视频 JDK安装与环境变量的配置 IDEA社区的安装与使…

帝国CMS系统开启https后,无法登陆后台的原因和解决方法

今天本地配置好了帝国CMS7.5&#xff0c;传去服务器后&#xff0c;使用http访问一切正常。但是当开启了https&#xff08;SSL&#xff09;后&#xff0c;后台竟然无法登陆进去了。 输入账号密码后&#xff0c;点击登陆&#xff0c;跳转到/e/admin/ecmsadmin.php就变成页面一片…