以下是计数排序的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格:
一、计数排序基础实现
原理
通过统计每个元素的出现次数,按顺序累加得到每个元素的最终位置,并填充到结果数组中。
代码示例
java
public class CountingSort {
void sort(int[] arr) {
if (arr.length == 0) return;
// 找到数据范围
int min = Arrays.stream(arr).min().getAsInt();
int max = Arrays.stream(arr).max().getAsInt();
int range = max - min + 1;
// 统计每个元素出现的次数
int[] count = new int[range];
for (int num : arr) {
count[num - min]++;
}
// 累加统计结果,得到每个元素的最终位置
for (int i = 1; i < count.length; i++) {
count[i] += count[i - 1];
}
// 反向填充结果数组以保持稳定性
int[] output = new int[arr.length];
for (int i = arr.length - 1; i >= 0; i--) {
int num = arr[i];
output[count[num - min] - 1] = num;
count[num - min]--;
}
// 将结果复制回原数组
System.arraycopy(output, 0, arr, 0, arr.length);
}
}
复杂度分析
- 时间复杂度 :
O(n + k)
(n
为元素数量,k
为值域范围)。 - 空间复杂度 :
O(k)
。 - 稳定性:稳定(相同值的元素相对顺序不变)。
二、常见变体及代码示例
1. 支持负数的计数排序
改进点 :通过偏移量处理负数,扩展适用范围。
适用场景:数据包含负值。
java
public class CountingSortWithNegatives {
void sort(int[] arr) {
if (arr.length == 0) return;
int min = Arrays.stream(arr).min().getAsInt();
int max = Arrays.stream(arr).max().getAsInt();
int range = max - min + 1;
int[] count = new int[range];
for (int num : arr) {
count[num - min]++; // 偏移量为min
}
for (int i = 1; i < count.length; i++) {
count[i] += count[i - 1];
}
int[] output = new int[arr.length];
for (int i = arr.length - 1; i >= 0; i--) {
int num = arr[i];
output[count[num - min] - 1] = num;
count[num - min]--;
}
System.arraycopy(output, 0, arr, 0, arr.length);
}
}
2. 原地计数排序
改进点 :尝试在原数组上操作,减少额外空间。
适用场景:内存受限场景(但可能牺牲时间效率)。
java
public class InPlaceCountingSort {
void sort(int[] arr) {
if (arr.length == 0) return;
int min = Arrays.stream(arr).min().getAsInt();
int max = Arrays.stream(arr).max().getAsInt();
int range = max - min + 1;
int[] count = new int[range];
for (int num : arr) {
count[num - min]++;
}
// 直接在原数组上填充
int index = 0;
for (int i = 0; i < count.length; i++) {
while (count[i] > 0) {
arr[index++] = i + min;
count[i]--;
}
}
}
}
3. 并行计数排序
改进点 :利用多线程加速计数统计。
适用场景:超大数据集或并行计算环境。
java
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
public class ParallelCountingSort {
void sort(int[] arr) {
if (arr.length == 0) return;
int min = Arrays.stream(arr).min().getAsInt();
int max = Arrays.stream(arr).max().getAsInt();
int range = max - min + 1;
int[] count = new int[range];
// 并行统计
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(new CountTask(arr, count, 0, arr.length - 1, min));
// 累加统计结果
for (int i = 1; i < count.length; i++) {
count[i] += count[i - 1];
}
int[] output = new int[arr.length];
for (int i = arr.length - 1; i >= 0; i--) {
int num = arr[i];
output[count[num - min] - 1] = num;
count[num - min]--;
}
System.arraycopy(output, 0, arr, 0, arr.length);
}
// 并行统计任务
private static class CountTask extends RecursiveAction {
private final int[] arr;
private final int[] count;
private final int start;
private final int end;
private final int min;
CountTask(int[] arr, int[] count, int start, int end, int min) {
this.arr = arr;
this.count = count;
this.start = start;
this.end = end;
this.min = min;
}
@Override
protected void compute() {
if (end - start < 1000) { // 小区间直接统计
for (int i = start; i <= end; i++) {
count[arr[i] - min]++;
}
} else {
int mid = (start + end) / 2;
invokeAll(new CountTask(arr, count, start, mid, min),
new CountTask(arr, count, mid + 1, end, min));
}
}
}
}
三、变体对比表格
变体名称 | 时间复杂度 | 空间复杂度 | 稳定性 | 主要特点 | 适用场景 |
---|---|---|---|---|---|
基础计数排序 | O(n + k) |
O(k) |
稳定 | 简单易实现,适用于小范围数据 | 值域较小且无负数的场景 |
支持负数的计数排序 | O(n + k) |
O(k) |
稳定 | 扩展支持负数,需计算最小值 | 数据包含负值的场景 |
原地计数排序 | O(n + k) |
O(k) |
稳定 | 减少输出数组的使用,直接修改原数组 | 内存受限但时间允许的场景 |
并行计数排序 | O(n/k + k) (并行) |
O(k) |
稳定 | 利用多线程加速统计步骤 | 超大数据集或高性能计算环境 |
四、关键选择原则
- 基础场景:优先使用基础计数排序,因其简单且适用于小范围数据。
- 负数支持:当数据包含负值时,需使用支持负数的变体。
- 内存限制:原地排序适合内存紧张但允许额外统计数组的场景。
- 性能优化:并行计数排序适用于超大数据集或并行计算环境。
- 稳定性需求:所有变体均稳定,适用于需保持元素相对顺序的场景(如排序带键值的记录)。
通过选择合适的变体,可在特定场景下优化性能或扩展适用性。例如,并行计数排序在大数据集上显著提升速度,而支持负数的变体扩展了算法的通用性。