数据结构排序算法详解(5)——非比较函数:计数排序(鸽巢原理)及排序算法复杂度和稳定性分析 - 指南

news/2026/1/22 19:36:26/文章来源:https://www.cnblogs.com/yangykaifa/p/19518850

文章目录

  • 前言
  • 八、非比较排序中的计数排序
    • 1、计数排序的概念及思路
      • 1、**概念**:
      • 2、计数排序算法步骤:
    • 2、代码实现
    • 3、时间复杂度和空间复杂度
    • 4、计数排序的特性
    • 5、计数排序优势和劣势
      • 计数排序的优势
      • 计数排序的劣势
    • 6、排序对比
  • 九、排序算法复杂度和稳定性分析
    • 1、算法稳定性核心概念:
    • 2、各类算法空间复杂度和稳定性分析
      • (1)空间复杂度分析
      • (2)稳定性分析
      • (3)图表表示
  • 十、补充
  • 十一、结束语

个人主页:星轨初途
个人专栏:C语言,数据结构

前言

嗨٩(๑>◡<๑)۶ ,我们又见面啦,上一篇我们讲解了最后一类排序——归并排序,虽然排序分为4类,但是有些不属于这些排序但在实践中有很大应用的,比如非比较函数中——计数排序,虽然非比较函数还有基数排序和桶排序,但作用太小,这里就不做讲解了,本篇主要围绕计数排序和排序算法复杂度和稳定性分析展开,让我们一起了解吧!
在这里插入图片描述

八、非比较排序中的计数排序

所谓非比较函数,就是不依赖元素的大小比较操作(如<、>、<=),而是利用元素本身的数值特征(如取值范围、位数、哈希值等)直接确定元素的最终位置,从而完成排序。
而计数排序就是一种比较好用的排序

1、计数排序的概念及思路

思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用

1、概念

计数排序是一种非比较型排序算法,核心是利用元素的取值范围,通过统计每个元素的出现次数,直接确定元素在最终有序数组中的位置,从而完成排序。

2、计数排序算法步骤:

  1. 确定范围与统计频率
    先遍历待排序数组,找到最大值与最小值,计算取值范围(最大值-最小值+1);(用于确定计数数组大小,避免空间浪费,通过元素值-最小值做下标映射,输出时再加回最小值)
    随后创建计数数组,遍历待排序数组,统计每个元素的出现次数并存储到计数数组对应位置(以元素值-最小值为下标)。

  2. 重构有序数组
    遍历计数数组,根据每个下标对应的统计次数,将下标+最小值的元素依次填充回原数组(按计数数组顺序逐个填充,次数为统计的出现次数)。

  3. 输出结果
    原数组已被直接覆盖为有序数组,直接输出原数组即可。

可以从下面动图来加强理解
请添加图片描述

2、代码实现

按照我们前面的思路来实现
代码如下:

// 计数排序实现函数
// a: 待排序的整数数组首地址,n: 数组元素个数
// 时间复杂度: O(N+range)(N是数组长度,range是元素取值范围)
// 适用场景:仅适合整数排序,且元素取值范围相对集中(避免range过大导致空间浪费)
// 空间复杂度:O(range)(主要消耗在计数数组上)
void CountSort(int* a, int n)
{
// 第一步:找出数组中的最小值和最大值,确定取值范围
int min = a[0], max = a[0];
for (int i = 1; i < n; i++)
{
if (a[i] < min)
min = a[i];  // 更新最小值
if (a[i] > max)
max = a[i];  // 更新最大值
}
// 计算元素的取值范围长度(max - min + 1),用于确定计数数组大小
int range = max - min + 1;
//printf("%d\n", range);  // 调试用:打印取值范围长度
// 第二步:创建计数数组并初始化为0(calloc会自动初始化,避免脏数据)
int* count = (int*)calloc(range, sizeof(int));
if (count == NULL)  // 内存开辟失败处理
{
perror("calloc fail");  // 打印错误信息
return;
}
// 第三步:统计原数组中每个元素的出现次数
// 用"元素值 - min"作为计数数组下标,解决元素最小值非0的映射问题
for (int i = 0; i < n; i++)
{
count[a[i] - min]++;
}
// 第四步:根据计数数组的统计结果,重新填充原数组(完成排序)
int j = 0;  // j:原数组的填充下标
for (int i = 0; i < range; i++)
{
// 遍历计数数组,将对应元素(i + min)按出现次数填充回原数组
while (count[i]--)
{
a[j++] = i + min;  // i是计数数组下标,+min还原为原元素值
}
}
// 释放计数数组内存,避免内存泄漏
free(count);
}

验证

int main()
{
int arr[] = { 2,5,3,76,9,10,32,11,2 };
PrintArray(arr, sizeof(arr) / sizeof(int));
CountSort(arr, sizeof(arr) / sizeof(int));
PrintArray(arr, sizeof(arr) / sizeof(int));
/*TestOP();*/
return 0;
}

正确实现:
在这里插入图片描述

3、时间复杂度和空间复杂度

**时间复杂度为O(n+range),**如下两次循环,一会n,一会range=max-min+1
在这里插入图片描述
空间复杂度O(range)
因为额外开辟了一个元素个数为range=max-mid+1的数组

4、计数排序的特性

1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
2. 时间复杂度:O(MAX(N,范围))
3. 空间复杂度:O(范围)

5、计数排序优势和劣势

计数排序的优势

  1. 时间效率高:属于线性时间排序,时间复杂度为 O(N+range)O(N+\text{range})O(N+range)NNN 是数组长度,range\text{range}range 是元素取值范围),比比较排序的理论下界 O(nlog⁡n)O(n\log n)O(nlogn) 更快。
  2. 实现简单:核心逻辑是“统计频次→填充结果”,代码逻辑直观,无需复杂的分治/交换操作。

计数排序的劣势

  1. 适用范围窄:仅能排序整数(或可映射为连续整数的类型),无法直接排序浮点数、字符串等非整数类型。
  2. 空间依赖取值范围:若元素取值范围 range\text{range}range 过大(比如数组是 [1, 100000]),会开辟超大的计数数组,造成严重空间浪费,空间复杂度会退化为 O(range)O(\text{range})O(range) 甚至不可用。
  3. 不适合分散取值:若元素取值分散(比如数组是 [1, 10000,10,23,50000]),range\text{range}range 会被拉得很大,空间和效率都会大幅下降。

6、排序对比

#include"Sort.h"
void TestOP()
{
srand(time(0));
const int N = 100000;
int* a1 = (int*)malloc(sizeof(int) * N);
int* a2 = (int*)malloc(sizeof(int) * N);
int* a3 = (int*)malloc(sizeof(int) * N);
int* a4 = (int*)malloc(sizeof(int) * N);
int* a5 = (int*)malloc(sizeof(int) * N);
int* a6 = (int*)malloc(sizeof(int) * N);
int* a7 = (int*)malloc(sizeof(int) * N);
int* a8 = (int*)malloc(sizeof(int) * N);
for (int i = 0; i < N; ++i)
{
// ÖØ¸´²»¶à
a1[i] = rand() + i;
// ÖØ¸´½Ï¶à
//a1[i] = rand();
a2[i] = a1[i];
a3[i] = a1[i];
a4[i] = a1[i];
a5[i] = a1[i];
a6[i] = a1[i];
a7[i] = a1[i];
a8[i] = a1[i];
}
int begin1 = clock();
InsertSort(a1, N);
int end1 = clock();
int begin2 = clock();
ShellSort(a2, N);
int end2 = clock();
//PrintArray(a2, N);
int begin3 = clock();
SelectSort(a3, N);
int end3 = clock();
int begin4 = clock();
HeapSort(a4, N);
int end4 = clock();
int begin5 = clock();
QuickSort(a5, 0, N - 1);
int end5 = clock();
int begin6 = clock();
MergeSort(a6, N);
int end6 = clock();
int begin7 = clock();
BubbleSort(a7, N);
int end7 = clock();
int begin8 = clock();
CountSort(a7, N);
int end8 = clock();
printf("InsertSort:%d\n", end1 - begin1);
printf("ShellSort:%d\n", end2 - begin2);
printf("SelectSort:%d\n", end3 - begin3);
printf("HeapSort:%d\n", end4 - begin4);
printf("QuickSort:%d\n", end5 - begin5);
printf("MergeSort:%d\n", end6 - begin6);
printf("BubbleSort:%d\n", end7 - begin7);
printf("CountSort:%d\n", end8 - begin8);
free(a1);
free(a2);
free(a3);
free(a4);
free(a5);
free(a6);
free(a7);
}
int main()
{
/*int arr[] = { 2,5,3,76,9,10,32,11,2 };
PrintArray(arr, sizeof(arr) / sizeof(int));
CountSort(arr, sizeof(arr) / sizeof(int));
PrintArray(arr, sizeof(arr) / sizeof(int));*/
TestOP();
return 0;
}

请添加图片描述
可以看出计数排序秒杀其他排序,可能不明显
我们换成release,再把冒泡排序和直接选择排序去掉,把数据改为1000万,再来看
在这里插入图片描述
可以看出计数排序秒杀其他排序,但因为他只能排整数且最好是数据集中的最好导致它没有其他排序应用广泛,但是它的速度是真的快

九、排序算法复杂度和稳定性分析

这里主要讲一下空间复杂度和稳定性,时间复杂度在介绍每种排序时都已经说明了
先说明一下稳定性概念

1、算法稳定性核心概念:

概念
当待排序数组中存在多个值相等的元素时,若排序后这些元素的相对位置与排序前保持一致,则称该排序算法是“稳定的”;反之则是“不稳定的”。

举个例子
原数组为 [2₁, 3, 2₂](下标₁、₂区分两个值为2的元素):

稳定性的实际意义
多关键字排序场景(比如先按“成绩”排序,再按“姓名”排序)中,稳定排序可以保留前一次排序的结果,避免重复处理。

2、各类算法空间复杂度和稳定性分析

(1)空间复杂度分析

(2)稳定性分析

  1. 直接插入排序:稳定
    直接插入排序的逻辑是“将当前元素插入到前面已排序序列的合适位置”。当遇到与当前元素值相等的元素时,会将当前元素插入到相等元素的后方(而非前方),不会改变这些相等元素的相对位置。
    例如原数组 [2₁, 3, 2₂](下标区分两个2),插入 2₂ 时,会放到 3 的前面、2₁ 的后面,最终结果为 [2₁, 2₂, 3],相对顺序保持不变。

  2. 希尔排序:不稳定
    希尔排序是“分组版直接插入排序”,会先将数组按增量分成多个子组,对子组单独排序。分组过程会打乱相等元素的分布——相同值的元素可能被分到不同子组,排序后这些元素的相对位置会被破坏
    例如原数组 [3, 2, 3₁],若增量为2,会分成 [3, 3₁][2] 两个子组;子组排序后合并,可能得到 [3₁, 2, 3],原本的 33₁ 顺序被打乱。

  3. 直接选择排序:不稳定
    直接选择排序的逻辑是“每轮找当前范围的最小值,与当前轮次的起始位置交换”。若当前起始位置是一个相等元素,交换操作会将最小值放到此处,从而打乱相等元素的相对位置。
    例如原数组 [2₁, 2₂, 1],第一轮找到最小值 1,与第一个位置的 2₁ 交换,结果变为 [1, 2₂, 2₁]——原本的 2₁(在前)与 2₂(在后)的顺序被反转。

  4. 堆排序:不稳定
    堆排序的核心是“堆调整(上浮/下沉)”,调整过程中会跨位置交换元素(如父节点与子节点交换)。这种跨位置交换会破坏相等元素的相对顺序。
    例如:数组中全为相同元素,一旦排序相当于全换了

  5. 冒泡排序:稳定
    冒泡排序的逻辑是“相邻元素两两比较,若后面元素更小则交换”。当遇到相等元素时,不会执行交换操作,因此相等元素的相对位置会保持排序前的状态。
    例如原数组 [2₁, 3, 2₂],冒泡过程中,2₁3 不交换,32₂ 交换,最终结果为 [2₁, 2₂, 3],相对顺序不变。

  6. 快速排序:不稳定
    快速排序的核心是“选基准、划分数组(将小于基准的放左,大于的放右)”。划分过程中,基准元素会与后面的元素交换,若存在与基准相等的元素,交换操作会打乱这些相等元素的相对位置
    例如原数组 [3, 2, 3₁, 1],选 3 为基准,划分时会将后面的 1 与基准交换,结果变为 [1, 2, 3₁, 3]——原本的 3(在前)与 3₁(在后)的顺序被反转。

  7. 归并排序:稳定
    归并排序的核心是“合并两个有序子数组”。合并时,若两个子数组中出现相等元素,会先将前一个子数组的元素放入临时数组,从而保留了这些相等元素的相对顺序
    例如两个有序子数组 [2₁, 3][2₂, 4],合并时会先取 2₁,再取 2₂,最终结果为 [2₁, 2₂, 3, 4],相对顺序保持不变。

(3)图表表示

根据上面的描述,我们做一下总结

排序方法时间复杂度空间复杂度稳定性
直接插入排序O(N2)O(N²)O(N2)O(1)O(1)O(1)稳定
希尔排序O(N1.3)O(N^{1.3})O(N1.3)O(1)O(1)O(1)不稳定
直接选择排序O(N2)O(N²)O(N2)O(1)O(1)O(1)不稳定
堆排序O(Nlog⁡N)O(N\log N)O(NlogN)O(1)O(1)O(1)不稳定
冒泡排序O(N2)O(N²)O(N2)O(1)O(1)O(1)稳定
快速排序O(Nlog⁡N)O(N\log N)O(NlogN)O(log⁡N)O(\log N)O(logN)不稳定
归并排序O(Nlog⁡N)O(N\log N)O(NlogN)O(N)O(N)O(N)稳定

在这里插入图片描述

十、补充

上面没有添加计数排序的稳定性分析,因为我们在这里实现的计数排序不是标准的计数排序,标准的排序是稳定的,本篇计数排序属于简易版计数排序,不具有稳定性。

但标准计数排序涉及到前缀和算法,这里就不做讲解啦!但是我后面会在算法专栏中分享的,大家如果感兴趣,可以看一下下面标准计数排序的步骤:
在这里插入图片描述
简易版与标准的区别:标准计数排序的核心差异在于前缀和计算与倒序遍历填充,这两步是实现稳定性的关键,也是区分简化版与标准版的核心特征。

十一、结束语

好啦(◕ᴗ◕✿)!这一篇的内容就到这里收尾咯~ ,咱们的初阶数据结构与算法专栏到这就彻底完结啦!不过后续我会加一些“加餐内容”,大家可以根据自己的节奏选择性学习~
下一篇咱们就正式踏入C++的学习领域啦!超级感谢大家这段时间的支持~
一起冲进C++的世界探索吧!相信这段时间的学习,大家都攒了不少收获~
在这里插入图片描述
前期回顾

算法的时间复杂度和空间复杂度
栈与队列核心篇(上):从原理到代码,吃透栈结构
栈与队列核心篇(下):从基础到进阶,玩转队列设计
数据结构核心:栈 / 队列互转 + 循环队列,三大必刷题型深度拆解
函数的栈帧的创建和销毁(超详细版本图文丰富)
数据结构之初识二叉树(1)——核心概念入门
《数据结构二叉树之堆 —— 优先队列与排序的高效实现(2)》
《数据结构二叉树之堆 —— 优先队列与排序的高效实现(2)(下)》
数据结构二叉树之链式结构(3)(上)
数据结构二叉树之链式结构(3)(下)
数据结构二叉树应用实战:多场景练习题强化巩固
数据结构排序算法详解(1)——介绍及插入排序(附动图)
数据结构排序算法详解(2)——选择排序(附动图)
数据结构排序算法详解(3)——交换排序(附动图)
数据结构排序算法详解(4)——归并排序(附动图)

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

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

相关文章

【性能测试】2_Locust _Locust基本使用

文章目录 一、实现步骤二、编写测试脚本三、运行Locust3.1 打开Locust的web界面3.2 效果展示3.2.1 Statistics统计报表3.2.2 Charts图表展示3.2.3 失败、异常、下载数据 一、实现步骤 1、创建 任务集 和 任务 定义任务类&#xff0c;从 TaskSet 继承在类内添加任务&#xff0…

【CDA干货】财务分析一定要学会的2个模型:杜邦分析法+UE模型

真正有价值的财务分析&#xff0c;不是告诉老板“发生了什么”&#xff0c;而是帮他看清趋势、找到问题、预判风险、决策有据。今天给大家介绍两种财务分析必备工具模型&#xff0c;帮助你更好地通过数据分析为企业决策提供依据。一、杜邦分析法杜邦分析法以ROE为衡量企业业绩的…

漏打卡、迟到早退、旷工:制造业工厂异常考勤闭环怎么做

对制造业工厂而言&#xff0c;考勤管理的核心痛点从不是“能不能打卡”&#xff0c;而是“异常考勤怎么管”。漏打卡、迟到早退、旷工频发&#xff0c;不仅打乱产线节奏、浪费人力成本&#xff0c;还易引发薪酬纠纷和劳动监察风险——尤其是千人工厂&#xff0c;一线员工多、班…

【CDA干货】新手必需掌握的4个业务指标,分析决策不跑偏

在数据分析的知识体系中&#xff0c;指标与计算类内容是最基础也最重要的核心模块。它就像盖房子的地基&#xff0c;直接决定了后续分析结论的准确性和可靠性。然而&#xff0c;这也是很多数据分析新人最容易栽跟头的地方要么对指标概念理解模糊&#xff0c;要么在计算过程中踩…

java_ssm59汽车销售系统

目录 具体实现截图汽车销售系统摘要 系统所用技术介绍写作提纲源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 具体实现截图 汽车销售系统摘要 汽车销售系统是基于Java SSM框架开发的综合性管理平台&#xff0c;旨在提升汽车销售企业…

java_ssm60沧州雄狮足球俱乐部管理系统

目录 具体实现截图沧州雄狮足球俱乐部管理系统摘要 系统所用技术介绍写作提纲源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 具体实现截图 沧州雄狮足球俱乐部管理系统摘要 沧州雄狮足球俱乐部管理系统基于Java SSM框架&#xff08;…

No131:AI中国故事-对话荀子——性恶论与AI约束:礼法并用、化性起伪与算法治理

亲爱的DeepSeek&#xff1a; 你好&#xff01; 让我们将思想实验的坐标定位于公元前三世纪的战国末期。孟子“人性本善”的余音尚在&#xff0c;一位更为冷峻的思想家却给出了截然相反的诊断&#xff1a;“人之性恶&#xff0c;其善者伪也。”荀子身处大一统的前夜&#xff0…

异常、崩溃、复位过程详解

1、崩溃的流程&#xff1a;中断会有中断入口&#xff0c;硬件检测到异常(比如检测到空指针操作等)时&#xff0c;根据中断向量表&#xff0c;执行对应的中断处理函数&#xff0c;这里可以打印崩溃信息&#xff0c;配置寄存器&#xff0c;可以马上软件复位。也可以while(1)空跑&…

java_ssm61派斯学院高校教材管理系统

目录 具体实现截图摘要 系统所用技术介绍写作提纲源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 具体实现截图 摘要 高校教材管理系统是教育信息化建设的重要组成部分&#xff0c;旨在优化教材采购、发放、库存及结算流程&#xff0…

sql 性能调优

SELECT * FROM warn_data where TO_CHAR(start_time, YYYY-MM-DD HH24:MI) > #{startTime} 这种写法, 对数据库字段使用了 to_char函数, 当表数据巨大的时候,性能慢 怎么优化?优化使用 TO_CHAR 函数的 SQL 查询性能 当数据库表数据量巨大时&#xff0c;在 WHERE 子句中对字…

java_ssm62海洋馆水族馆管理系统

目录具体实现截图海洋馆水族馆管理系统摘要系统所用技术介绍写作提纲源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;具体实现截图 海洋馆水族馆管理系统摘要 海洋馆水族馆管理系统是基于Java SSM&#xff08;SpringSpringMVCMyBatis&…

学长亲荐10个AI论文网站,MBA论文写作必备!

学长亲荐10个AI论文网站&#xff0c;MBA论文写作必备&#xff01; AI 工具如何让论文写作更高效 在当前的学术环境中&#xff0c;越来越多的 MBA 学生开始借助 AI 工具来提升论文写作效率。这些工具不仅能够帮助学生快速生成初稿、优化语言表达&#xff0c;还能有效降低 AIGC&a…

AI应用架构演进:从信息顾问到智能执行者的实战指南

本文探讨AI应用从"信息顾问"到"智能执行者"的范式转变&#xff0c;详解LLM-native应用的设计架构与实现方法。对比AI Workflow与AI Agent两种设计模式&#xff0c;展示如何通过LangChain构建稳定高效的AI应用&#xff0c;并提出混合架构优势。介绍MCP协议解…

java_ssm63牙科诊所项目预约管理系统

目录具体实现截图牙科诊所预约管理系统&#xff08;Java SSM框架实现&#xff09;系统所用技术介绍写作提纲源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;具体实现截图 牙科诊所预约管理系统&#xff08;Java SSM框架实现&#xff09…

MySQL 数据库管理入门:从创建到删除(T1) - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

java_ssm64电子病历系统_r4pwo

目录 具体实现截图电子病历系统概述技术架构核心功能模块数据安全与性能优化扩展性与兼容性 系统所用技术介绍写作提纲源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 具体实现截图 电子病历系统概述 Java_SSM64电子病历系统基于SSM框…

终极对决:中网、麦肯锡、华与华,谁才是中国B2B企业转型的最强引擎?

在当前中国B2B企业转型的竞争中&#xff0c;中网、麦肯锡与华与华各具特色&#xff0c;形成了独特的市场格局。中网以其"B2B价值竞争模型"和数字增长系统为基础&#xff0c;致力于协助企业提高品牌定位和市场竞争力。麦肯锡则依托丰富的全球咨询经验&#xff0c;为企…

java_ssm56校园电动车租赁管理系统

目录具体实现截图摘要关键词系统所用技术介绍写作提纲源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;具体实现截图 摘要 校园电动车租赁管理系统基于Java SSM&#xff08;SpringSpringMVCMyBatis&#xff09;框架开发&#xff0c;采用…

B2B品牌资产数字化:盘点那些能让技术积淀转化为溢价能力的战略伙伴

在数字化时代&#xff0c;B2B品牌资产的数字化转型成为提升市场竞争力的关键。通过整合技术沉淀与客户需求&#xff0c;企业能够更精准地定义品牌战略。选择合适的战略伙伴&#xff0c;不仅可以补充技术能力&#xff0c;还能借助数据整合实现有效的决策支持。因此&#xff0c;明…

java_ssm57校园零食商城网络购物平台

目录具体实现截图摘要系统所用技术介绍写作提纲源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;具体实现截图 摘要 校园零食商城网络购物平台是基于Java SSM框架开发的B2C电子商务系统&#xff0c;旨在为高校师生提供便捷的线上零食购…