计数排序
比如对一个班的学生成绩进行倒着排序:
1、找到最高分96分,找到最低分11分
2、申请一个数组,数组大小为96-11=85,将11~96都填充到该数组中,并在该表格(临时数组)中统计每个分数出现的次数,比如27分出现的次数为0,那么在此表格中记录它的次数为0,如果有2个同学考了58分,则记录出现的次数为2。
3、再找到每个分数比它自己低的分数的个数。比如11分为最低分,比11分低的分数个数为0。以此类推,如果是分数相同的,则比自己成绩还低的个数上要累加,如果有2个58分的,对于元素组中的第一个是第6名,原数组中的第二个58则为第7名。以此类推...
分数 | 该成绩出现的次数 | 比自己还低成绩的个数 |
11 | 1 | 0 |
12 | 1 | 1 |
13 | 0 | 2 |
14 | 0 | 2 |
... | ... | ... |
41 | 1 | 5 |
... | ... | ... |
58 | 2 | 7(注意,此处特殊) |
59 | 0 | 8 |
... | ... | ... |
75 | 1 | 8 |
... | ... | ... |
计数排序的思想
计数排序的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定)。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。例如,如果输入序列中只有17个元素的值小于x的值,则x可以直接存放在输出序列的第18个位置上。
计数排序的实现过程
计数排序是一种牺牲空间换取时间的做法,需要依赖一个临时数组
1、找到数组的最小值和最大值,确定数据范围
2、开辟一个对应数据范围大小的辅助数组
3、遍历数组,统计每个数字出现的次数
4、计算每个元素在排序完成后的位置
4.1 引入一个变量用于记录该数字前面有多少个比自己还小的元素。
5、遍历数组,将每个数字放到它的最终位置
5.1 创建一个和原有数组相同元素个数的数组。遍历原有数组,并按照统计结果放到对应的索引位置上。比如第一个元素8,我们统计到前面有5个元素比它小,那么直接将其放到索引为5的位置上即可。如果第三个元素8,我们统计到有5个元素比8小,但结果数组中已经有一个8了,那么直接将第三个元素直接放到索引为6的位置上即可(两个元素不能挤在同一个位置)。
实现案例
对数组进行从小到大的顺序排列:
81, 94, 11, 96, 12, 58, 35, 17, 95, 28, 58, 41, 75, 15
参考代码
注意每行打印都很重要,一定要把代码拷出来自己在电脑上运行下,借助于我添加的打印进行辅助理解。
//
// Created by bibo on 24-8-15.
//
#include <stdio.h> // 用于printf()函数
#include <stdlib.h> // 用于malloc()函数
void CountSort(int nums[], int numsSize) {
if (numsSize <= 1 || nums == NULL) {
return;
}
int max = nums[0];
int min = nums[0];
// 找到最大值和最小值
for (int i = 0; i < numsSize; i++) {
if (nums[i] >= max) {
max = nums[i];
}
if (nums[i] <= min) {
min = nums[i];
}
}
printf("\n最大值:%d,最小值:%d", max, min);
// 根据最大值和最小值确定范围并创建一个数组用于统计每个元素出现的次数
int range = max - min + 1;
int* countNum = malloc(sizeof(int) * range);
printf("\n创建临时数组:");
for (int i = 0; i < range; i++) {
countNum[i] = 0;
// min++;
printf("%d ", countNum[i]);
}
// 每个整数出现的次数统计到计数数组中对应下标的位置
printf("\n开始统计次数:");
for (int i = 0; i < numsSize; i++) {
countNum[nums[i] - min] += 1;
printf("[%d,%d] ", nums[i], countNum[nums[i] - min]);
}
printf("\n对应位置次数:");
for (int i = 0; i < range; i++) {
printf("%d ", countNum[i]);
}
printf("\n比自己小的数:");
int preCounts = 0; // preCounts代表某个元素前面有多少个比自己小的数字。
for (int i = 0; i < range; i++) {
preCounts += countNum[i];
countNum[i] = preCounts - countNum[i];
printf("%d ", countNum[i]);
}
int* result = malloc(sizeof(int) * numsSize);
printf("\n对于原数组排序:");
for (int i = 0; i < numsSize; i++) {
result[countNum[nums[i] - min]] = nums[i];
printf("[元素%d,名次%d] ", result[countNum[nums[i] - min]],countNum[nums[i] - min]);
countNum[nums[i] - min] += 1;
}
for (int i = 0; i<numsSize;i++) {
nums[i] = result[i];
}
}
int main() {
printf("计数排序算法(Counting Sort Algorithm)\n");
int nums[] = {81, 94, 11, 96, 12, 58, 35, 17, 95, 28, 58, 41, 75, 15};
printf("排序前:");
for (int i = 0; i < 13; ++i) {
printf("%d ", nums[i]);
}
CountSort(nums, 13);
printf("\n排序后:");
for (int i = 0; i < 13; ++i) {
printf("%d ", nums[i]);
}
return 0;
}
打印结果
该算法较难理解,可以根据打印结果辅助理解代码逻辑。
排序前:81 94 11 96 12 58 35 17 95 28 58 41 75
最大值:96,最小值:11
创建临时数组:0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
开始统计次数:[81,1] [94,1] [11,1] [96,1] [12,1] [58,1] [35,1] [17,1] [95,1] [28,1] [58,2] [41,1] [75,1]
对应位置次数:1 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1
比自己小的数:0 1 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 11 12
对于原数组排序:[元素81,名次9] [元素94,名次10] [元素11,名次0] [元素96,名次12] [元素12,名次1] [元素58,名次6] [元素35,名次4] [元素17,名次2] [元素95,名次11] [元素28,名次3] [元素58,名次7] [元素41,名次5] [元素75,名次8]
排序后:11 12 17 28 35 41 58 58 75 81 94 95 96