写在前面
书接上文:【数据结构与算法】排序算法(中)——交换排序之快速排序
文章主要讲解计数排序的细节与分析源码。之后进行四大排序的总结。
文章目录
- 写在前面
- 一、计数排序(非比较排序)
- 代码的实现:
- 二、排序总结
- 2.1、稳定性
- 3.2、排序算法复杂度及稳定性总结
一、计数排序(非比较排序)
思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。
操作步骤:
-
确定范围:首先,确定待排序数组中元素的取值范围,即最小值和最大值。假设数据的值在
[0, K-1]范围内。 -
创建计数数组:根据元素的取值范围创建一个计数数组
count,该数组的每个索引代表数组中某个元素的值,值为该元素出现的次数。例如,count[i]表示元素i出现的次数。 -
统计元素出现次数:遍历原始数组,对每个元素
x,将count[x]增加 1。这样,count数组就记录了每个元素的出现次数。 -
重建排序数组:遍历
count数组,根据每个元素的出现次数将元素放入最终的排序数组中。

假设有一个待排序数组[4, 2, 2, 8, 3, 3, 1]。
步骤 1:确定数据范围
最小值是 1,最大值是 8,因此我们需要创建一个长度为 8 的计数数组 count,用于记录 1 到 8 每个元素的出现次数。
步骤 2:设计计数数组
在步骤1中确定了数组开辟长度为 8。在8个长度中,我们应该把下标0就设置存储最小元素出现的次数,接下来的下标就对应着当前数据-最小元素的下标。
- 举个例子:
在当前数组中:1是最小元素,那么计数数组的0下标就对应着统计1元素出现的次数,那么元素8对应的小标就是8-1的位置,即下标7才是计算元素8出现的次数。
步骤 3:统计每个元素的出现次数
遍历原始数组 [4, 2, 2, 8, 3, 3, 1],统计每个元素出现的次数,并更新计数数组:
- 数字
4:count[4-1] = count[3]增加1,count[3] = 1 - 数字
2:count[2-1] = count[1]增加1,count[1] = 1 - 数字
2:count[2-1] = count[1]增加1,count[1] = 2 - 数字
8:count[8-1] = count[7]增加1,count[7] = 1 - 数字
3:count[3-1] = count[2]增加1,count[2] = 1 - 数字
3:count[3-1] = count[2]增加1,count[2] = 2 - 数字
1:count[1-1] = count[0]增加1,count[0] = 1
统计完成后,count 数组变为:count = [1, 2, 2, 1, 0, 0, 0, 1]

这表示:
- 数字
1出现了1次 - 数字
2出现了2次 - 数字
3出现了2次 - 数字
4出现了1次 - 数字
5没有出现 - 数字
6没有出现 - 数字
7没有出现 - 数字
8出现了1次
步骤 4:根据计数数组重建排序数组
接下来,我们可以根据 count 数组来重建排序数组:
从 count 数组中取出每个数字出现的次数,然后把它们按顺序放入结果数组中。
-
count[0] = 1,因此排序数组中加入一个1。 -
count[1] = 2,因此排序数组中加入两个2。 -
count[2] = 2,因此排序数组中加入两个3。 -
count[3] = 1,因此排序数组中加入一个4。 -
count[4] = 0,跳过。 -
count[5] = 0,跳过。 -
count[6] = 0,跳过。 -
count[7] = 1,因此排序数组中加入一个8。
重建后的原数组: [1, 2, 2, 3, 3, 4, 8]

代码的实现:
void countingSort(int* arr, int len) {
int min = arr[0], max = arr[0];//
for (int i = 0; i < len; i++) {//找最大最小值
if (arr[i] > max) {
max = arr[i];
}
if (arr[i] < min) {
min = arr[i];
}
}
int* countArr = (int*)calloc((max - min) + 1, sizeof(int));//创建一个计数数组,用于记录每个数字出现的次数。
for (int i = 0; i < len; i++) {//计数
countArr[arr[i] - min]++;
}
int j = 0;
for (int i = 0; i < (max - min) + 1; i++) {//排序
/*if (countArr[i] <= 0) {
;
}
else {
arr[j++] = i + min;
countArr[i]--;
i--;
}*/
while (countArr[i]--) {
arr[j++] = i + min;
}
}
}
- 需要注意的细节在最小值与最大值的定义时,需要初始化为数组中的元素,只有这样才会对排序不会造成影响。
- 在排序中,可以设计一个循环来进行辅助排序,也可以使用条件判断语句排序,这二者的时间复杂度都是一样的。
- 在计数循环中,使用了
arr[i] - min,这样是把min最小的值设置为数组的开头,即下标为0的位置,这样就可以避免计数数组开辟过多的空间,也可以更好的适配含负数的数据排序
计数排序的特性总结:
- 计数排序在数据范围集中时,效率很高,甚至可以达到O(N),但是适用范围及场景有限。
- 时间复杂度:O(MAX(N,范围))
- 空间复杂度:O(范围)
- 稳定性:稳定
二、排序总结
2.1、稳定性
在不才写的三篇排序笔记中,不才在每个排序中都有写特性总结,这其中有稳定性,在排序中,稳定性并不是说是这个排序是否每次都稳定排序,而是指在排序过程中,若两个元素的值相等,它们在排序后的相对顺序保持不变。。

我们排序 { 1 2 3 1 4 } 这组数据,在这组数据中,如果我们使用的是稳定的排序,排序完成后,红色的1一定会保持在橙色的1后面,如:{ 1 1 2 3 4 };如果不能确保每次排序红色的1在橙色的1后面,那就是不稳定排序,如:{ 1 1 2 3 4 }。
3.2、排序算法复杂度及稳定性总结


ps:
排序算法(上)——插入排序与选择排序
排序算法(中)——交换排序与归并排序
以上就是本章所有内容。若有勘误请私信不才。万分感激💖💖 如果对大家有帮助的话,就请多多为我点赞收藏吧~~~💖💖

ps:表情包来自网络,侵删🌹



















