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

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

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

  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. 重构数组:根据统计结果,按顺序填充原数组

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

相关推荐
Monkey-旭2 小时前
Android Bitmap 完全指南:从基础到高级优化
android·java·人工智能·计算机视觉·kotlin·位图·bitmap
Mike_Wuzy7 小时前
【Android】发展历程
android
开酒不喝车7 小时前
安卓Gradle总结
android
阿华的代码王国8 小时前
【Android】PopupWindow实现长按菜单
android·xml·java·前端·后端
稻草人不怕疼10 小时前
Android 15 全屏模式适配:A15TopView 自定义组件分享
android
静默的小猫10 小时前
LiveDataBus消息事件总线之二-(不含反射和hook)
android
~央千澈~11 小时前
05百融云策略引擎项目交付-laravel实战完整交付定义常量分文件配置-独立建立lib类处理-成功导出pdf-优雅草卓伊凡
android·laravel·软件开发·金融策略
_一条咸鱼_11 小时前
Android Runtime冷启动与热启动差异源码级分析(99)
android·面试·android jetpack
用户20187928316711 小时前
Java序列化之幽灵船“Serial号”与永生契约
android·java
用户20187928316711 小时前
“对象永生”的奇幻故事
android·java