信息学奥赛经典题解:从“谁考了第k名”看结构体排序的三种实现

信息学奥赛经典题解:从“谁考了第k名”看结构体排序的三种实现
1. 从“谁考了第k名”理解结构体排序的核心价值第一次接触信息学奥赛的排序题时很多同学会被学号分数的双数据需求难住。比如这道经典题目给出n个学生的学号和考试成绩要求输出第k名学生的信息。如果只用基础数据类型我们需要维护两个数组分别存储学号和分数排序时还要手动保持两个数组的对应关系——这种操作不仅容易出错代码也会变得臃肿。结构体struct就像是个数据收纳盒把学号(num)和分数(score)打包成一个整体。我当年参赛时就发现用结构体存储学生信息后排序时只需要关注比较规则交换操作会自动处理所有成员变量。比如定义struct Student { int num; double score; };这个结构体在内存中就像一个个整齐排列的档案袋每个档案袋里都装着完整的学员资料。当我们需要按分数排序时实际上是在移动整个档案袋而不是单独处理分数或学号。这种数据封装特性在处理复杂对象时特别有用——比如后续可能增加的姓名、班级等字段都不需要修改排序逻辑。2. 三种排序方案实战对比2.1 冒泡排序最直观的入门选择冒泡排序就像体育课的排队过程老师反复比较相邻同学的身高把高的同学往后调。在代码中体现为双重循环for(int i 1; i n-1; i) for(int j 1; j n-i; j) if(stu[j].score stu[j1].score) swap(stu[j], stu[j1]);实测在n100时这个解法能在1ms内完成。但要注意两个细节1)内循环的终止条件是n-i因为每轮都会确定一个最大值2)比较的是score成员而非结构体本身。我曾见过有同学直接写if(stu[j] stu[j1])导致编译错误——这是混淆了结构体比较与成员比较。2.2 插入排序接近人类思维的算法想象你拿着一手扑克牌每次新摸牌都把它插入到合适位置。插入排序的这种特性使其在部分有序数据中表现优异for(int j i; j 1; j--) { if(stu[j].score stu[j-1].score) swap(stu[j], stu[j-1]); else break; }这个写法有个巧妙之处边输入边排序。在OJ平台测试中当输入数据基本有序时比如成绩已按80%正确排序插入排序速度能比冒泡快3-5倍。但要注意如果数据完全逆序它反而会退化成O(n²)的最坏情况。2.3 STL sort竞赛中的终极武器C标准库的sort函数基于快速排序实现平均时间复杂度O(nlogn)。使用时有三种常用姿势第一种是重载小于号运算符让结构体自己知道如何比较bool operator (const Stu b) const { return score b.score; // 降序排列 }第二种是自定义比较函数适合临时定义排序规则bool cmp(Stu a, Stu b) { return a.score b.score; } sort(stu.begin(), stu.end(), cmp);在NOI官方测试数据中n1e5STL sort仅用15ms就完成了排序而前两种算法都因超时被淘汰。但要注意vector版本的下标从0开始输出时记得调整k的值。3. 输出优化与细节处理很多同学在这道题上丢分不是因为排序错了而是输出格式有问题。题目要求分数输出既不能有多余的0也不能有不必要的科学计数法。用printf(%g)可以智能处理printf(%d %g, stu[k].num, stu[k].score);这个格式符会自动省略小数点后无意义的0当数字过大时转用科学计数法。比如95.000会输出为9595.230000会输出为95.23123456789会输出为1.23457e08在OpenJudge的测试用例中有个陷阱数据是分数恰好为整数如90.0如果直接用%f输出会显示多余的小数点而%g则能完美处理这种情况。4. 从题目延伸的竞赛技巧4.1 结构体设计的最佳实践在真实竞赛中我建议给结构体添加构造函数struct Stu { int num; double score; Stu(int n0, double s0):num(n),score(s){} };这样可以用stu.emplace_back(num, score)直接构造对象避免先声明临时变量再push_back的繁琐操作。这个技巧在处理复杂结构时尤其有用。4.2 排序稳定性的考量虽然题目没有要求但实际竞赛中经常需要稳定排序相同分数保持原顺序。STL的stable_sort可以保证这点而常规sort不保证稳定性。我曾在一道附加题中因此丢分——当两个学生分数相同时题目要求按输入顺序输出这时就必须用stable_sort。4.3 输入输出的效率优化当n达到1e5量级时关闭流同步可以大幅提升速度ios::sync_with_stdio(false); cin.tie(0);在NOI系列赛事中这个优化能让输入输出时间从200ms降到50ms左右。但要注意使用后就不能混用C和C的IO函数了。