当前位置: 首页 > news >正文

计数排序进阶:不仅要排序,还要知道它排在第几位(稳定性详解)

前言

在信息学奥赛(OI)和CSP认证中,计数排序(Counting Sort)是一种非常高效的非比较排序算法。它的时间复杂度仅为O(n+w)(其中w是值域),在处理值域较小整数排序时,速度远超快速排序。

很多同学学会了如何用桶统计频率并输出排序结果,但在遇到“输出原数组中每个元素在排序后所处的位置”这类问题时,往往会卡在如何保证稳定性上。

今天我们就结合一道经典例题,来讲讲计数排序的核心——前缀和与倒序遍历


问题描述

给定n个正整数,请将它们从小到大排序,然后输出。

进阶要求:并且输出原来每个数字排序后在第几位(对于相同的数字,序号小的排在前面)。

样例输入

5 3 9 5 3 2

样例输出

2 3 3 5 9 2 5 4 3 1

(解释:原数组第1个是3,排序后排在第2位;第2个是9,排序后排在第5位...)


核心逻辑解析

计数排序不仅是“数数”,它的完整形态包含三个步骤:

  1. 统计频率c[x]++,统计数字x出现了几次。

  2. 前缀和(关键)c[i]+=c[i-1]

    • 做完这一步,c[x]的含义发生了质变:它不再是x的个数,而是x在有序数组中最后一次出现的位置(右边界)

    • 比如样例中,数字3出现了 2 次。做完前缀和后,c[3]=3,意味着所有数字 3 应该排在第2位和第3位,且最后一个3必须坐在第3把交椅上

  3. 倒序确定排名(稳定性)

    • 为什么要从n到1倒着遍历原数组?

    • 因为前缀和数组c[x]告诉我们要把x放在最靠后的位置。

    • 当我们遇到原数组中靠后出现的x时,应该把它填入当前x能占用的最大位置,然后将c[x]--(把这个位置封死,留给前面出现的x)。

    • 这样就完美保证了:原数组中靠后的,排序后依然靠后(稳定性)。


完整代码

//排序 用记数排序来写 #include <iostream> #include <algorithm> //max和min函数 using namespace std; int n; int a[100010];//愿数组 int c[100010];//前缀和数组,记录小于等于i的数字有多少个 int r[100010];//记录原来第i个数排完序后的位置 int ma=0; int mi=0x3f3f3f3f; int main() { cin>>n; for(int i=1;i<=n;i++){ cin>>a[i]; c[a[i]]++; ma=max(ma,a[i]);//找原数中的最大值 mi=min(mi,a[i]);//找原数中的最小值 } //第一步先输出排序后的序列 for(int i=mi;i<=ma;i++){ for(int j=1;j<=c[i];j++){ cout<<i<<" "; } } cout<<endl; //第二步处理:计算排名(核心) //c[i]现在的含义是:数值小于等于i的元素一共有多少个 //也就是数值i在排序后能达到的最大位置(右边界) //对c数组做前缀和,求出小于等于i的数有多少个 for(int i=2;i<=ma;i++){ c[i]+=c[i-1]; } //倒序遍历原数组,确定每个数的排名 //必须倒着做,这是保证算法稳定性的关键 for(int i=n;i>=1;i--){ //a[i]是当前要处理的数字 //c[a[i]]是它当前可用的最靠后的位置 //占用了这个位置后,该数字的可用位置就要向前移一位 r[i]=c[a[i]]--; } //输出每个原位置数字的排名 for(int i=1;i<=n;i++){ cout<<r[i]<<" "; } }

总结

  1. 数据范围:计数排序受限于值域。本题中,空间完全够用。如果很大(如 10^9),则需要使用map或离散化,或者改用sort

  2. 空间换时间:这是典型的用空间换时间的算法。

  3. 易错点

    • 前缀和循环的范围不要越界。

    • 最重要的一点:最后填充r[i]时,一定要写i=n循环到1。虽然顺着写在某些特定题目(不需要稳定性)也能过,但养成倒序遍历的习惯是理解基数排序的基础。

http://www.jsqmd.com/news/318879/

相关文章:

  • 基于Simulink的车与车(V2V)通信仿真(信息交互场景)
  • 基于Simulink的电机绕组绝缘优化仿真
  • C++模拟器开发实践
  • 2026大模型就业指南:技术演进、核心技能与职业规划
  • AI大模型应用开发学习路线路径,巨详细!你要悄悄努力然后惊艳所有人
  • NOR Flash芯片GT25Q40汽车电子车载存储方案
  • 使用Fabric自动化你的部署流程
  • TI DLP光机模组之DLP3010
  • SPI NOR Flash和SPI NAND Flash存储芯片的区别
  • C++代码依赖分析
  • C++中的组合模式变体
  • redis集群有几种模式?分别讲讲这些集群模式的基本原理是什么?
  • Transformer架构:每个模块到底在解决什么问题?
  • 使用Python处理计算机图形学(PIL/Pillow)
  • TCN-Transformer-GRU组合模型回归+SHAP分析+新数据预测+多输出!深度学习可解释分析MATLAB代码
  • 【读书笔记】《大流感》
  • 设计模式在C++中的实现
  • 核心注解
  • Rocky Linux 9 双网卡 bond0 绑定 - 实践
  • 用Python批量处理Excel和CSV文件
  • 自定义字面量高级用法
  • 用Pygame开发你的第一个小游戏
  • 零成本抽象在C++中的应用
  • C++中的组合模式
  • W3C XML 活动
  • C++中的代理模式实现
  • 同源策略 ≠ 万能盾牌:为什么你的后端仍需防范“盲打“攻击?
  • 【AI】在RK3576上,使用RKNN实现MeloTTS(文本转语音)
  • C++与Python混合编程实战
  • 高性能序列化库