计数排序故事:运动会的奖牌统计

一、运动会的故事:如何快速统计奖牌数量

学校运动会结束后,需要统计各个班级获得的奖牌数量(金牌、银牌、铜牌)。传统方法是一个一个数,效率很低。体育老师想到了一个办法:

  1. 准备三个盒子:分别标上 "金牌"、"银牌"、"铜牌"

  2. 分类投放:让每个班级把奖牌对应放入三个盒子中

  3. 统计结果:直接数每个盒子里的奖牌数量,得到最终排名

这就是计数排序的核心思想:通过统计每个元素的出现次数来实现排序

二、计数排序的 Java 代码实现

java

ini 复制代码
public class CountingSort {
    public static void sort(int[] array) {
        if (array == null || array.length == 0) return;
        
        // 1. 找出数组中的最大值和最小值
        int max = array[0];
        int min = array[0];
        for (int num : array) {
            if (num > max) max = num;
            if (num < min) min = num;
        }
        
        // 2. 创建计数数组,大小为(max - min + 1)
        int[] countArray = new int[max - min + 1];
        
        // 3. 统计每个元素出现的次数
        for (int num : array) {
            countArray[num - min]++;  // 偏移量为min,确保数组索引从0开始
        }
        
        // 4. 根据计数数组重构原数组
        int index = 0;
        for (int i = 0; i < countArray.length; i++) {
            while (countArray[i] > 0) {
                array[index++] = i + min;  // 还原实际值
                countArray[i]--;
            }
        }
    }
}

三、计数排序的核心步骤解析

1. 确定范围

找出数组中的最大值和最小值,确定计数数组的大小:

java

ini 复制代码
// 示例数组:[4, 2, 2, 8, 3, 3, 1]
max = 8, min = 1
计数数组大小 = 8 - 1 + 1 = 8
2. 统计次数

遍历原数组,统计每个元素出现的次数:

java

arduino 复制代码
// 计数数组初始状态:[0, 0, 0, 0, 0, 0, 0, 0]
// 遍历后:[1, 2, 2, 0, 1, 0, 0, 1]
// 索引对应值: 1  2  3  4  5  6  7  8
3. 重构数组

根据计数数组的结果,重新填充原数组:

java

arduino 复制代码
// 重构过程:
// 索引0值为1,原数组填充1个1 → [1]
// 索引1值为2,原数组填充2个2 → [1, 2, 2]
// 索引2值为2,原数组填充2个3 → [1, 2, 2, 3, 3]
// 索引3值为0,跳过
// 索引4值为1,原数组填充1个4 → [1, 2, 2, 3, 3, 4]
// ...
// 最终结果:[1, 2, 2, 3, 3, 4, 8]

四、计数排序的性能分析

  • 时间复杂度:O (n + k),其中 n 是元素个数,k 是数值范围

  • 空间复杂度:O (k),主要用于计数数组

  • 稳定性:可以实现稳定排序(通过改进算法)

  • 适用条件

    • 数据范围较小(k 远小于 n)
    • 非负整数(或可映射到非负整数)

五、计数排序的优化与变种

1. 稳定计数排序

在重构数组时,从后向前遍历原数组,可以保证相同元素的相对顺序不变:

java

ini 复制代码
public static void stableSort(int[] array) {
    if (array == null || array.length == 0) return;
    
    // 找出最大值和最小值
    int max = array[0], min = array[0];
    for (int num : array) {
        if (num > max) max = num;
        if (num < min) min = num;
    }
    
    // 创建计数数组
    int[] countArray = new int[max - min + 1];
    
    // 统计次数
    for (int num : array) {
        countArray[num - min]++;
    }
    
    // 计算前缀和(用于确定元素在结果数组中的位置)
    for (int i = 1; i < countArray.length; i++) {
        countArray[i] += countArray[i - 1];
    }
    
    // 创建临时数组,保存排序结果
    int[] temp = new int[array.length];
    for (int i = array.length - 1; i >= 0; i--) {
        int num = array[i];
        int position = countArray[num - min] - 1;
        temp[position] = num;
        countArray[num - min]--;
    }
    
    // 复制回原数组
    System.arraycopy(temp, 0, array, 0, array.length);
}
2. 处理负数

如果数组包含负数,可以通过偏移量将所有数映射为非负数:

java

arduino 复制代码
// 示例:处理包含负数的数组 [-3, 2, -1, 0, 5]
// 最小值为-3,偏移量为3
// 映射后:[0, 5, 2, 3, 8]
// 排序后再映射回原数值

六、计数排序 vs 其他排序算法

排序算法 时间复杂度 空间复杂度 稳定性 适用场景
计数排序 O(n + k) O(k) 稳定 数据范围小的整数排序
快速排序 O(n log n) O(log n) 不稳定 通用场景
归并排序 O(n log n) O(n) 稳定 大规模数据、链表排序
插入排序 O(n²) O(1) 稳定 小规模或基本有序数据

七、总结:计数排序的核心思想

计数排序的本质是 "用空间换时间":

  1. 统计次数:用一个数组记录每个元素出现的次数

  2. 重构数组:根据统计结果,按顺序填充原数组

就像运动会统计奖牌,通过分类投放和直接计数,快速得到结果。计数排序特别适合处理数据范围较小的整数排序,效率远超比较类排序算法(如快速排序、归并排序)。但它的局限性在于需要额外空间存储计数数组,且不适用于浮点数、字符串等复杂数据类型。

相关推荐
一只柠檬新1 小时前
Web和Android的渐变角度区别
android
志旭1 小时前
从0到 1实现BufferQueue GraphicBuffer fence HWC surfaceflinger
android
_一条咸鱼_1 小时前
Android Runtime堆内存架构设计(47)
android·面试·android jetpack
用户2018792831671 小时前
WMS(WindowManagerService的诞生
android
用户2018792831671 小时前
通俗易懂的讲解:Android窗口属性全解析
android
openinstall1 小时前
A/B测试如何借力openinstall实现用户价值深挖?
android·ios·html
二流小码农1 小时前
鸿蒙开发:资讯项目实战之项目初始化搭建
android·ios·harmonyos
志旭2 小时前
android15 vsync源码分析
android
志旭3 小时前
Android 14 HWUI 源码研究 View Canvas RenderThread ViewRootImpl skia
android
whysqwhw3 小时前
Egloo 高级用法
android