写在前面
书接上文:【数据结构与算法】排序算法(中)——交换排序之快速排序
文章主要讲解计数排序的细节与分析源码。之后进行四大排序的总结。
文章目录
- 写在前面
- 一、计数排序(非比较排序)
- 代码的实现:
- 二、排序总结
- 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:表情包来自网络,侵删🌹