八大排序之——计数排序全方位剖析!(小白也能轻松看懂!)

微软技术分享
优质创作者: 编程框架技术领域
领域专家: 操作系统技术领域
2025-01-23 20:42:59

1. 计数排序的思想动图

https://i-blog.csdnimg.cn/direct/6e613e0fa54f450da3833327dc13a93e.gif

2. 从思想到代码的实现

https://i-blog.csdnimg.cn/direct/1177b843b8df4286bf61f922ee5ccc99.png

>1.创建临时数组

我们先创建一个临时的数组tmp并且该数组的最大下标为原数组中元素的最大值(里面的元素初始化为0)

    //先遍历原数组找到最大值
    int max = a[0];
    for (int i = 1; i < n; i++)
    {
        if (max < a[i])
        {
            max = a[i];
        }
    }
    //动态内存开辟max+1个int空间并初始化为0用calloc
    int* tmp = (int)calloc(max+1, sizeof(int));
    if (tmp == NULL)
    {
        perror("calloc fail!\n");
        return;
    }

>2.统计次数

然后遍历一遍原数组,原数组中出现哪个元素就在tmp数组中与该元素数值相同的下标对应的地方进行加加操作来记录该元素出现的次数。

    //统计次数
    for (int i = 0; i < n; i++)
    {
        tmp[a[i]]++;
    }

>3.排序

最后我们再进行排序,记住tmp的下标对应原数组可能出现的元素,而里面的值对应该元素出现的次数,如果为0就说明该元素在原数组不存在。遍历tmp数组,将对应的下标覆盖原数组即可完成排序。

//排序
int j = 0;
for (int i = 0; i < max + 1; i++)//第一层循环遍历tmp数组
{
    while (tmp[i]--)//对应元素出现的次数
    {
        a[j++] = i;//tmp的下标对应的就是原数组可能出现的元素
    }
}
//释放资源,避免内存泄漏
free(tmp);
tmp = NULL;

>4.简单版本

好现在简单的计数排序就已经完成了

完整代码:

void CountSort(int* a,int n)
{
    //先遍历原数组找到最大值
    int max = a[0];
    for (int i = 1; i < n; i++)
    {
        if (max < a[i])
        {
            max = a[i];
        }
    }
    //动态内存开辟max+1个int空间并初始化为0用calloc
    int* tmp = (int*)calloc(max+1, sizeof(int));
    if (tmp == NULL)
    {
        perror("calloc fail!\n");
        return;
    }
    //统计次数
    for (int i = 0; i < n; i++)
    {
        tmp[a[i]]++;
    }
    //排序
    int j = 0;
    for (int i = 0; i < max + 1; i++)//第一层循环遍历tmp数组
    {
        while (tmp[i]--)//对应元素出现的次数
        {
            a[j++] = i;//tmp的下标对应的就是原数组可能出现的元素
        }
    }
    //释放资源,避免内存泄漏
    free(tmp);
    tmp = NULL;
}

https://i-blog.csdnimg.cn/direct/81ae726a88004fbfa172b82461af729e.png

3. 是否可以优化呢~

思考一下,如果我们的数据是这样的呢,如果还像之前那样开辟空间,是否会有大量的空间浪费呢?遍历的次数增大,是否有性能的降低呢~

https://i-blog.csdnimg.cn/direct/ce19acb5ef3746b8a652f89ef0ae1b36.png

所以,我们就可以这样取tmp的范围:

https://i-blog.csdnimg.cn/direct/38b625e03ace4dcb99d8bf9439e3c13b.png

优化后代码: 

//先遍历原数组找到最大值
int max = a[0];
int min = a[0];
for (int i = 1; i < n; i++)
{
    if (max < a[i])
    {
        max = a[i];
    }
    if (min > a[i])
    {
        min= a[i];
    }
}
int range = max - min + 1;
//动态内存开辟max+1个int空间并初始化为0用calloc
int* tmp = (int*)calloc(range, sizeof(int));
if (tmp == NULL)
{
    perror("calloc fail!\n");
    return;
}

 关于下标与元素对应的问题,我们就可以采用相对映射的方法来解决(优化后代码)

https://i-blog.csdnimg.cn/direct/2a1750ffe6a8462c955dd81f05cfac8b.png

https://i-blog.csdnimg.cn/direct/b9f410b19b8c47f9924dc9a33a0bacbd.png

优化后的完整代码:

void CountSort(int* a,int n)
{
    //先遍历原数组找到最大值
    int max = a[0];
    int min = a[0];
    for (int i = 1; i < n; i++)
    {
        if (max < a[i])
        {
            max = a[i];
        }
        if (min > a[i])
        {
            min= a[i];
        }
    }
    int range = max - min + 1;
    //动态内存开辟max+1个int空间并初始化为0用calloc
    int* tmp = (int*)calloc(range, sizeof(int));
    if (tmp == NULL)
    {
        perror("calloc fail!\n");
        return;
    }
    //统计次数
    for (int i = 0; i < n; i++)
    {
        tmp[a[i]-min]++;
    }
    //排序
    int j = 0;
    for (int i = 0; i < range; i++)//第一层循环遍历tmp数组
    {
        while (tmp[i]--)//对应元素出现的次数
        {
            a[j++] = i+min;//tmp的下标对应的就是原数组可能出现的元素
        }
    }
    //释放资源,避免内存泄漏
    free(tmp);
    tmp = NULL;
}

https://i-blog.csdnimg.cn/direct/7900c396d92d4af789d694d1fe5e2e4c.png

4. 计数排序的时空复杂度

空间复杂度:除原数组外,计数排序额外开辟了一个大小为N的临时空间,所以计数排序的空间复杂度为O(N)。

时间复杂度:遍历找最大最小值取范围的时间复杂度为O(N),遍历原数组统计次数的时间复杂度为O(N),而排序里面,虽有两层循环,但从思想和本质来看,它只不过是将原来的元素按顺序覆盖了原数组,其执行循环的次数任然为O(N),所以计数排序的时间复杂度为O(N).

5.总结

计数排序(Counting Sort)是一种非比较排序算法,它的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

计数排序的优点

  • 稳定性:计数排序是一种稳定的排序算法,即相等的元素在排序过程中不会改变相对位置。
  • 时间复杂度低:在数据范围不是很大的情况下,计数排序的时间复杂度可以达到O(n),是非常高效的排序算法。
  • 空间换时间:计数排序通过使用额外的空间来换取排序时间的大幅度降低,适合数据范围不大的情况。

计数排序的局限性

  • 数据范围限制:当数据的范围非常大时,计数排序需要创建一个长度为最大值加一的数组,这将导致大量的空间浪费,因此它不适合数据范围很大的排序。
  • 数据类型限制:计数排序只适用于整数排序,对于其他类型的数据(如字符串、浮点数等)则不适用。
  • 空间复杂度较高:计数排序需要额外的存储空间来存储临时数组,当数据范围较大时,其空间复杂度会显著增加。

综上所述,计数排序在特定条件下(数据范围不大且为整数)是非常高效的排序算法,但在数据范围大或数据类型不匹配时则不适用。在实际应用中,需要根据具体的数据特征选择合适的排序算法。


文章来源: https://blog.csdn.net/2301_80221228/article/details/140345975
版权声明: 本文为博主原创文章,遵循CC 4.0 BY-SA 知识共享协议,转载请附上原文出处链接和本声明。


...全文
273 回复 打赏 收藏 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
内容概要:本文介绍了一个基于Python与数字孪生技术的工业旅游园区运行监测系统的设计与实现。系统通过整合生产设备、环境传感器、客流统计和能源消耗等多源数据,构建统一的数据体系,并利用数字孪生模型实现园区的虚拟映射。系统架构涵盖数据采集、治理、时序存储、对象建模、智能分析与可视化展示五个层次,集成了数据清洗、异常检测(如Isolation Forest)、客流预测(如随机森林回归)、环境质量评价和综合运行评分等功能。通过FastAPI提供服务接口,支持实时监测、预警响应与科学决策,推动园区管理从人工向数据驱动转型。文中还提供了完整的数据生成、特征工程、模型训练与接口开发的代码示例。; 适合人群:具备Python编程基础,熟悉数据分析、机器学习及Web开发的技术人员,适用于高校学生、软件工程师、智能制造与智慧城市领域研究人员;尤其适合从事工业互联网、数字孪生、智慧园区等相关项目的1-3年工作经验开发者。; 使用场景及目标:①实现工业旅游园区的实时运行状态监控与三维可视化展示;②对客流、设备健康、环境舒适度与安全风险进行预测与预警;③为园区调度、应急管理、能源优化和游客服务提供数据支持与决策依据;④作为数字孪生教学案例,用于学习数据处理、模型构建与系统集成全流程。; 阅读建议:此资源结合理论模型与代码实践,建议读者在学习过程中同步运行示例代码,深入理解数据清洗、特征构造、算法建模与接口开发的关键步骤,并可进一步扩展至真实硬件接入、三维引擎集成与大规模部署应用。

8,333

社区成员

发帖
与我相关
我的任务
社区描述
微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。
windowsmicrosoft 企业社区
社区管理员
  • 微软技术分享
  • 郑子铭
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。

予力众生,成就不凡!微软致力于用技术改变世界,助力企业实现数字化转型。

试试用AI创作助手写篇文章吧