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

微软技术分享 微软全球最有价值专家
全栈领域优质创作者
博客专家认证
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 知识共享协议,转载请附上原文出处链接和本声明。


...全文
59 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

3,165

社区成员

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

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

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

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