手撕排序算法:计数排序


计数排序(Counting Sort)是一种基于哈希的排序算法。它的基本思想是通过统计每个元素的出现次数,然后根据统计结果将元素依次放入排序后的序列中。这种排序算法适用于元素范围较小的情况,例如整数范围在0到k之间。

1.算法思想

计数排序的核心是创建一个计数数组,用于记录每个元素出现的次数。然后,根据计数数组对
原始数组进行排序。具体步骤如下:
第一步、初始化一个长度为最大元素值加1的计数数组,所有元素初始化为0。
第二步、遍历原始数组,将每个元素的值作为索引,在计数数组中对应位置加1。
第三步、将原数组清空。
第四步、遍历计数器数组,按照数组中元素个数放回到原数组中。

2.算法分析

2.1时间复杂度

我们假设一次「哈希」和「计数」的时间复杂度均为O(1)。并且总共个数,数字范围为
1→k。除了输入输出以外,「计数排序」中总共有四个循环。
第一个循环,用于初始化「计数器数组」,时间复杂度O(K);
第二个循环,枚举所有数字,执行「哈希」和「计数」操作,时间复杂度O();
第三个循环,枚举所有范围内的数字,时间复杂度O(k);
第四个循环,是嵌套在第三个循环内的,最多走O(),虽然是嵌套,但是它可第三个循环是相加的关系,而并非相乘的关系。
所以,总的时间复杂度为:O(n+k)

2.2 空间复杂度

假设最大数为k,空间复杂度为O(k)

3.算法的优缺点

3.1算法的优点

  1. 简单易懂:计数排序的原理非常简单,容易理解和实现。
  2. 时间复杂度低:对于范围较小的元素,计数排序的时间复杂度非常优秀。
  3. 适用于特定场景:当元素的范围已知且较小时,计数排序可以高效地完成排序。

3.2算法的缺点

  1. 空间开销:计数排序需要额外的空间来存储计数数组,当元素范围较大时,可能会消耗较多的内存。
  2. 局限性:计数排序只适用于元素范围较小的情况,对于大规模数据或元素范围不确定的情况并不适用。

4.优化思路

「计数排序」在众多排序算法中效率最高,时间复杂度为O(+k)。
但是,它的缺陷就是非常依赖它的数据范围。必须为整数,且限定在[1,k]范围内,所以由于
内存限制,k就不能过大,优化点都是常数优化了,主要有两个:
(1)初始化「计数器数组」可以采用系统函数(例如C/C++中的memset,纯内存操作,优于
循环);
(2)上文提到的第三个循环,当排序元素达到个时,可以提前结束,跳出循环。

5.代码实现

void CountingSort(int nums[],int numsSize,int m) {  
    // [0,1,2,3,...,m]  
    int* count = (int)malloc(sizeof((int)*(m+1)));  
    memset(count,0,sizeof((int)*(m+1)));  
    for (int i = 0; i < numsSize; i++) {  
        count[nums[i]]++;  
    }  
    int idx = 0;  
    for (int i = 0; i <= m; i++) {  
        while (count[i]) {  
            nums[idx++] = i;  
            count[i]--;  
        }  
    }  
    free(count);  
}

6.实战

6.1 力扣75.颜色分类

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,**原地**对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
必须在不使用库内置的 sort 函数的情况下解决这个问题

void CountingSort(int nums[],int numsSize,int m) {
    // [0,1,2,3,...,m]
    int* count = (int)malloc(sizeof(int)*(m+1));
    memset(count,0,sizeof(int)*(m+1));
    for (int i = 0; i < numsSize; i++) {
        count[nums[i]]++;
    }
    int idx = 0;
    for (int i = 0; i <= m; i++) {
        while (count[i]) {
            nums[idx++] = i;
            count[i]--;
        }
    }
    free(count);
}
void sortColors(int* nums, int numsSize) {
    CountingSort(nums,numsSize,2);
}

6.2力扣1046.最后一块石头的重量

有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出两块最重的石头,然后将它们一起粉碎。假设石头的重量分别为x和y,且×<=y。那么粉碎的可
能结果如下:
·如果x=y,那么两块石头都会被完全粉碎:
·如果x!=y,那么重量为x的石头将会完全粉碎,而重量为y的石头新重量为y-x。
最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回0。

void CountingSort(int nums[],int numsSize,int m) {
    // [0,1,2,3,...,m]
    int* count = (int)malloc(sizeof(int)*(m+1));
    memset(count,0,sizeof(int)*(m+1));
    for (int i = 0; i < numsSize; i++) {
        count[nums[i]]++;

    }
    int idx = 0;
    for (int i = 0; i <= m; i++) {
        while (count[i]) {
            nums[idx++] = i;
           count[i]--;
        }
    }
    free(count);
}

int lastStoneWeight(int* stones, int stonesSize) {
    while(stonesSize > 1){
        CountingSort(stones,stonesSize,31);
        int v = stones[stonesSize - 1] - stones[stonesSize - 2];
        stonesSize -=2;
        if(v != 0 || stonesSize == 0){
            stones[stonesSize] = v;
            stonesSize++;
        }
    }
    return stones[0];
}
posted @ 2024-07-16 01:00  写代码的大学生  阅读(7)  评论(0编辑  收藏  举报  来源