排序算法之计数排序


title: 计数排序

date: 2024-7-26 09:39:30 +0800

categories:

  • 排序算法
    tags:
  • 排序
  • 算法
  • 计数排序
    description: 计数排序(Counting sort)是一种稳定的线性时间排序算法。该算法于1954年由[哈罗德·H·西华德]提出。计数排序使用一个额外的数组𝐶,其中第i个元素是待排序数组𝐴中值等于𝑖的元素的个数。然后根据数组𝐶来将𝐴中的元素排到正确的位置。
    math: true

计数排序

计数排序(Counting Sort)是一种线性时间复杂度的排序算法。它适用于排序一定范围内的整数,特别是当范围不大时,表现非常高效。

计数排序的原理

计数排序的主要思想是通过统计每个元素的出现次数,计算出每个元素在排序后数组中的位置,从而实现排序。计数排序适用于数据范围相对较小且数据分布比较均匀的情况。

图示

计数排序的步骤

  1. 找出数组中的最大值和最小值:确定数据的范围。
  2. 创建计数数组:计数数组的大小为最大值和最小值之间的差加1,用于记录每个元素的出现次数。
  3. 统计每个元素的出现次数:遍历待排序数组,填充计数数组。
  4. 计算元素的最终位置 :对计数数组进行累加处理,得到每个元素在排序后数组中的位置。前缀和具有明确的意义,prefix[num] - 1 代表元素 num 在结果数组 res 中最后一次出现的索引。这个信息非常关键,因为它告诉我们各个元素应该出现在结果数组的哪个位置。
  5. 构建输出数组:根据计数数组的信息,将原数组的元素放到正确的位置上。
  6. 复制到原数组:将输出数组复制回原数组,完成排序。

计数排序示例

step1
step2
step3
step4
step5
step6
step7
step8

复杂度分析

当输入的元素是𝑛个0到𝑘之间的整数时,它的运行时间是 Θ ( n + k ) \Theta (n+k) Θ(n+k)。计数排序不是比较排序,因此不被 Ω ( n log ⁡ n ) \Omega (n\log n) Ω(nlogn)的下界限制。

  • 时间复杂度为 O ( n + k ) O(n + k) O(n+k)、非自适应排序 :涉及遍历 nums 和遍历 counte ,都使用线性时间。一般情况下 n ≫ k n \gg k n≫k ,时间复杂度趋于 O ( n ) O(n) O(n) 。
  • 空间复杂度为 O ( n + k ) O(n + k) O(n+k)、非原地排序 :借助了长度分别为 n n n 和 m m m 的数组 rescounte
  • 稳定排序 :由于向 res 中填充元素的顺序是"从右向左"的,因此倒序遍历 nums 可以避免改变相等元素之间的相对位置,从而实现稳定排序。实际上,正序遍历 nums 也可以得到正确的排序结果,但结果是非稳定的。

时间复杂度

  • 最佳情况 : O ( n + k ) O(n+k) O(n+k)。
  • 最坏情况 : O ( n + k ) O(n+k) O(n+k)。
  • 平均情况 : O ( n + k ) O(n+k) O(n+k)。

空间复杂度

  • 空间复杂度 : O ( n + k ) O(n+k) O(n+k)。

局限性

计数排序非常巧妙,仅通过统计数量就可以实现高效的排序。然而,使用计数排序的前置条件相对较为严格。

计数排序只适用于非负整数。若想将其用于其他类型的数据,需要确保这些数据可以转换为非负整数,并且在转换过程中不能改变各个元素之间的相对大小关系。例如,对于包含负数的整数数组,可以先给所有数字加上一个常数,将全部数字转化为正数,排序完成后再转换回去。

计数排序适用于数据量大但数据范围较小的情况 。比如,在上述示例中 k k k 不能太大,否则会占用过多空间。而当 n ≪ k n \ll k n≪k 时,计数排序使用 O ( k ) O(k) O(k) 时间,可能比 O ( n log ⁡ n ) O(n \log n) O(nlogn) 的排序算法还要慢。

Java代码实现

java 复制代码
import java.util.Arrays;

public class CountingSort {

    public static void countingSort(int[] nums) {
        int n = nums.length;

        // 找出数组中的最大值和最小值
        int max = Arrays.stream(nums).max().getAsInt();
        int min = Arrays.stream(nums).min().getAsInt();

        // 创建计数数组,大小为 (max - min + 1)
        int range = max - min + 1;
        // counter[num] 代表 num 的出现次数
        int[] counter = new int[range];
        // 初始化数组 res 用于记录结果
        int[] res = new int[n];

        // 统计每个元素的出现次数
        for (int num : nums) {
            counter[num - min]++;
        }

        // 计算元素的最终位置(累加处理)
        // 求 counter 的前缀和,将"出现次数"转换为"尾索引"
        // 即 counter[num]-1 是 num 在 res 中最后一次出现的索引
        for (int i = 1; i < counter.length; i++) {
            counter[i] += counter[i - 1];
        }
		
        // 倒序遍历 arr ,将各元素填入结果数组 res 
        for (int i = n - 1; i >= 0; i--) {
	        int num = nums[i] - min; 
	        // 将 num 放置到对应索引处 
	        res[counter[num] - 1] = num;
	        // 令前缀和自减 1 ,得到下次放置 num 的索引 
	        counter[num]--; 
        }

        // 将输出数组复制回原数组
        System.arraycopy(res, 0, nums, 0, n);
    }

    public static void main(String[] args) {
        int[] arr = {4, 2, 2, 8, 3, 3, 1};
        System.out.println("Given Array:");
        System.out.println(Arrays.toString(arr));

        // 调用计数排序函数
        countingSort(arr);

        System.out.println("Sorted Array:");
        System.out.println(Arrays.toString(arr));
    }
}
相关推荐
pianmian13 小时前
贪心算法.
算法·贪心算法
chenziang14 小时前
leetcode hot 100 二叉搜索
数据结构·算法·leetcode
single5945 小时前
【c++笔试强训】(第四十五篇)
java·开发语言·数据结构·c++·算法
呆头鹅AI工作室6 小时前
基于特征工程(pca分析)、小波去噪以及数据增强,同时采用基于注意力机制的BiLSTM、随机森林、ARIMA模型进行序列数据预测
人工智能·深度学习·神经网络·算法·随机森林·回归
一勺汤7 小时前
YOLO11改进-注意力-引入自调制特征聚合模块SMFA
人工智能·深度学习·算法·yolo·目标检测·计算机视觉·目标跟踪
每天写点bug7 小时前
【golang】map遍历注意事项
开发语言·算法·golang
程序员JerrySUN7 小时前
BitBake 执行流程深度解析:从理论到实践
linux·开发语言·嵌入式硬件·算法·架构
王老师青少年编程8 小时前
gesp(二级)(16)洛谷:B4037:[GESP202409 二级] 小杨的 N 字矩阵
数据结构·c++·算法·gesp·csp·信奥赛
robin_suli8 小时前
动态规划子序列问题系列一>等差序列划分II
算法·动态规划
cxylay9 小时前
自适应滤波算法分类及详细介绍
算法·分类·自适应滤波算法·自适应滤波·主动噪声控制·anc