计数排序
计数排序(Counting Sort)是一种非基于比较的排序算法,适用于待排序的数值较小的场景。计数排序的核心思想是将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
以下是计数排序的详细步骤:
确定待排序数据的范围:
首先,你需要知道待排序数组中的最大值和最小值,以确定整数的范围。例如,如果数组中的最小值为0,最大值为k,则整数的范围为0到k。
初始化计数数组:
创建一个大小为k+1的计数数组count,并将其所有元素初始化为0。这个数组将用于统计每个整数在输入数组中出现的次数。
计数:
遍历输入数组,对于数组中的每个元素x,将count[x]增加1。这样,count[i]就表示数值i在输入数组中出现的次数。
计算排序后每个元素的位置:
修改计数数组,使得每个count[i]表示在排序后的数组中,小于或等于i的元素数量。这通常通过从count[0]到count[k]的累加操作来实现。
排序:
创建一个新的输出数组,然后再次遍历输入数组。对于输入数组中的每个元素x,将其放在输出数组中的count[x] - 1位置上,并将count[x]减1。这样,相同的元素会被依次放置在连续的位置上,而较小的元素会先被放置,从而实现排序。
返回排序后的数组:
经过上述步骤,输出数组就已经是按升序排列的原始输入数组了。
计数排序的特点:
时间复杂度:计数排序的时间复杂度为O(n+k),其中n是待排序元素的个数,k是待排序元素的取值范围。当k的值不是很大时,计数排序是非常高效的。
空间复杂度:由于需要额外的计数数组,所以空间复杂度为O(k)。
稳定性:计数排序是稳定的排序算法,即相等的元素在排序后会保持原有的相对顺序。
局限性:计数排序只适用于整数的排序,且当k的值非常大时,算法的空间复杂度会变得很高,这可能使得算法在实际应用中变得不可行。
总的来说,计数排序在处理小范围整数的排序时非常高效,特别是当需要排序的数据量很大且数据范围较小时。然而,当数据范围较大时,由于空间复杂度的限制,计数排序可能不是最优选择。
代码实现
过程:
初始化:函数首先初始化max和min为数组的第一个元素。
寻找最大最小值:通过遍历数组,找到数组中的最大值和最小值,以确定后续计数数组的大小。
计算范围并分配内存:根据找到的最大值和最小值,计算出数组中数值的范围(range),并动态分配一个大小为range的整数数组count用于计数。
计数:再次遍历原数组,统计每个元素出现的次数。这里的关键是将元素值减去最小值min,以得到在计数数组中的索引。
排序:根据计数数组中的值,重新填充原数组,实现排序。这个过程从计数数组的最小索引开始,按照出现次数将对应的元素值(索引加上min)填充回原数组。
释放内存:最后,释放计数数组所占用的内存,以避免内存泄漏。
// 计数排序函数,对整数数组a中的n个元素进行排序
void CountSort(int* a, int n) {
// 初始化最大值和最小值为数组的第一个元素
int max = a[0], min = a[0];
// 遍历数组,找出数组中的最大值和最小值
for (int i = 0; i < n; i++) {
if (a[i] > max) {
max = a[i]; // 更新最大值
}
if (a[i] < min) {
min = a[i]; // 更新最小值
}
}
// 计算数组中数值的范围(即最大值与最小值之间的差加上1)
int range = max - min + 1;
// 动态分配一个大小为range的整数数组,用于计数
int* count = (int*)malloc(sizeof(int) * range);
if (count == NULL) {
// 如果内存分配失败,打印错误信息并返回
perror("malloc fail");
return;
}
// 使用memset函数将计数数组初始化为0
memset(count, 0, sizeof(int) * range);
// 遍历原数组,统计每个元素出现的次数
// 注意:这里利用元素值减去最小值min作为计数数组的索引
for (int i = 0; i < n; i++) {
count[a[i] - min]++;
}
int j = 0; // 用于追踪排序后数组的填充位置
// 遍历计数数组,根据计数重新填充原数组,实现排序
for (int i = 0; i < range; i++) {
// 对于计数数组中的每个元素,根据其值(出现次数)重复填充原数组
while (count[i]--) {
a[j] = i + min; // 注意:填充的是i+min,而不是count[i]
j++; // 移动到下一个填充位置
}
}
// 释放计数数组的内存(避免内存泄漏)
free(count);
}
这个计数排序算法的时间复杂度为O(n+k),其中n是待排序元素的个数,k是待排序元素的取值范围。当k的值不是很大时,计数排序是非常高效的。
使用示例
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void PrintArray(int* a, int n)//打印
{
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
void CountSort(int* a, int n) {
int max = a[0], min = a[0];
for (int i = 0; i < n; i++) {
if (a[i] > max) {
max = a[i];
}
if (a[i] < min) {
min = a[i];
}
}
int range = max - min + 1;
int* count = (int*)malloc(sizeof(int) * range);
if (count == NULL) {
perror("malloc fail");
return;
}
//初始化数组为0
memset(count, 0, sizeof(int) * range);
for (int i = 0; i < n; i++) {
count[a[i] - min]++;
}
int j = 0;
for (int i = 0; i < range; i++) {
while (count[i]--) {
a[j] = i + min;//注意这里不是count[i],而是i
j++;
}
}
}
void TestCountSort()
{
int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };
PrintArray(a, sizeof(a) / sizeof(int));//计算数组元素个数并打印
CountSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
int main() {
TestCountSort();
return 0;
}