一、什么是计数排序?
计数排序(Counting Sort)是一种非比较排序算法 ,它的核心思想是通过统计每个元素出现的次数,直接生成有序数组,不需要进行任何元素之间的比较。
简单来说,计数排序就像"按号码排队":
- 你有一堆号码牌,号码从1到8;
- 准备9个桶(0到8),每个桶对应一个号码;
- 把每个号码牌放到对应的桶里,统计每个桶里有多少个号码;
- 从小到大依次把桶里的号码拿出来,就得到了有序的序列。
二、计数排序的核心步骤
计数排序的核心步骤可以分为以下三步:
- 准备桶 :先找出数组中的最大值,创建一个大小为
最大值+1的桶数组,初始值全为0; - 计数:遍历数组,每个元素对应桶的位置加1,统计每个元素出现的次数;
- 输出:从小到大遍历桶数组,把每个桶里的元素逐个取出,生成有序数组。
三、计数排序的代码实现
1. C++版本
cpp
#include <vector>
#include <algorithm>
using namespace std;
vector<int> counting_sort(vector<int>& arr) {
if (arr.empty()) return {};
// 1. 准备桶:找出最大值,创建桶数组
int max_val = *max_element(arr.begin(), arr.end());
vector<int> count(max_val + 1, 0);
// 2. 计数:统计每个元素出现的次数
for (int num : arr) {
count[num]++;
}
// 3. 输出:从小到大生成有序数组
vector<int> sorted_arr;
for (int i = 0; i < count.size(); i++) {
for (int j = 0; j < count[i]; j++) {
sorted_arr.push_back(i);
}
}
return sorted_arr;
}
2. Python版本
python
def counting_sort(arr):
if not arr:
return []
# 1. 准备桶:找出最大值,创建桶数组
max_val = max(arr)
count = [0] * (max_val + 1)
2. 计数:统计每个元素出现的次数
for num in arr:
count[num] += 1
3. 输出:从小到大生成有序数组
sorted_arr = []
for i in range(len(count)):
sorted_arr.extend([i] * count[i])
return sorted_arr
测试
arr = [3, 5, 2, 8, 2, 5, 1, 3]
print("排序前:", arr)
sorted_arr = counting_sort(arr)
print("排序后:", sorted_arr) # 输出:[1, 2, 2, 3, 3, 5, 5, 8]
3. C语言版本
c
#include <stdio.h>
#include <stdlib.h>
// 找出数组中的最大值
int find_max(int arr[], int n) {
int max = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
// 计数排序
void counting_sort(int arr[], int n) {
if (n <= 0) return;
// 1. 准备桶:找出最大值,创建桶数组
int max_val = find_max(arr, n);
int* count = (int*)malloc((max_val + 1) * sizeof(int));
for (int i = 0; i <= max_val; i++) {
count[i] = 0;
}
// 2. 计数:统计每个元素出现的次数
for (int i = 0; i < n; i++) {
count[arr[i]]++;
}
// 3. 输出:从小到大生成有序数组
int index = 0;
for (int i = 0; i <= max_val; i++) {
for (int j = 0; j < count[i]; j++) {
arr[index++] = i;
}
}
free(count);
}
// 打印数组
void print_array(int arr[], int n) {
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {3, 5, 2, 8, 2, 5, 1, 3};
int n = sizeof(arr) / sizeof(arr[0]);
printf("排序前:");
print_array(arr, n);
counting_sort(arr, n);
printf("排序后:");
print_array(arr, n); // 输出:1 2 2 3 3 5 5 8
return 0;
}
四、计数排序的特点
- 时间复杂度:O(n + k),其中n是元素个数,k是最大值;
- 空间复杂度:O(k),需要额外的桶数组;
- 是否比较:不需要进行任何元素之间的比较;
- 适用场景:元素范围较小的整数数组,如考试分数、年龄、号码牌等;
- 稳定性:稳定,相同元素的相对位置保持不变。
五、计数排序的优化
为了提高计数排序的效率,可以进行以下优化:
- 偏移优化:如果数组中的元素不是从0开始,可以计算偏移量,减少桶的大小;
- 稳定优化:通过前缀和的方式,保持相同元素的相对位置;
- 多轮计数:对于多位数的排序,可以使用计数排序作为基数排序的子过程。
六、计数排序的实际应用场景
计数排序是一种高效的排序算法,常见场景包括:
- 考试分数排序:学生的考试分数通常在0到100之间,适合用计数排序;
- 年龄排序:人口统计中的年龄排序,年龄范围通常较小;
- 号码牌排序:活动中的号码牌排序,号码范围固定;
- 基数排序:作为基数排序的子过程,用于多位数的排序。
七、计数排序 vs 比较排序
计数排序和比较排序是两种不同的排序算法,它们的区别如下:
| 特性 | 计数排序 | 比较排序 |
|---|---|---|
| 是否比较 | 否 | 是 |
| 时间复杂度 | O(n + k) | O(n log n) 或 O(n²) |
| 空间复杂度 | O(k) | O(1) 或 O(n) |
| 适用场景 | 元素范围较小的整数 | 任意元素类型 |
| 稳定性 | 稳定 | 不一定 |
八、总结
计数排序是一种非比较排序算法,它通过统计每个元素出现的次数,直接生成有序数组,不需要进行任何元素之间的比较。
计数排序的时间复杂度为O(n + k),空间复杂度为O(k),适合元素范围较小的整数数组,是一种高效的排序算法。
希望这篇文章能帮助你理解计数排序的原理和实现!