手撕排序算法:计数排序
文章目录
计数排序(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算法的优点
- 简单易懂:计数排序的原理非常简单,容易理解和实现。
- 时间复杂度低:对于范围较小的元素,计数排序的时间复杂度非常优秀。
- 适用于特定场景:当元素的范围已知且较小时,计数排序可以高效地完成排序。
3.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];
}