信息学奥赛刷题实战:P1068分数线划定,教你用冒泡排序手撕一遍(含稳定排序原理)

发布时间:2026/6/9 1:41:24

信息学奥赛刷题实战:P1068分数线划定,教你用冒泡排序手撕一遍(含稳定排序原理) 信息学奥赛刷题实战P1068分数线划定从冒泡排序到稳定排序的深度解析在信息学奥赛的备战过程中算法题目的解决往往需要扎实的基础知识和灵活的思维。P1068分数线划定这道经典题目表面上看是一个简单的排序问题但深入探究会发现其中蕴含着排序算法稳定性的重要概念。本文将从最基础的冒泡排序实现入手逐步剖析稳定排序的原理及其在实际问题中的关键作用帮助你在刷题过程中不仅能够AC更能理解算法背后的本质。1. 理解题目与排序需求P1068题目要求我们根据考生的成绩和报名号进行排序最终确定分数线并输出合格考生名单。具体规则如下首先按成绩从高到低排序成绩相同的考生按报名号从小到大排序分数线为排名第⌊m×1.5⌋位考生的成绩输出所有成绩不低于分数线的考生信息这种多条件排序在实际编程竞赛中非常常见理解如何正确实现这种排序是算法学习的基础。我们先来看一个简单的输入输出示例输入样例6 3 1000 90 3239 88 2390 95 7231 84 1005 95 1001 90输出样例88 5 1005 95 2390 95 1000 90 1001 90 3239 882. 冒泡排序的手动实现虽然现代编程语言都提供了高效的排序函数但手动实现排序算法对于理解算法原理至关重要。我们首先用最基础的冒泡排序来解决这个问题。2.1 冒泡排序基础版冒泡排序的核心思想是通过相邻元素的比较和交换将较大的元素逐步冒泡到数组的末端。以下是基础版的实现步骤void bubbleSortBasic(int scores[], int ids[], int n) { for (int i 0; i n-1; i) { for (int j 0; j n-i-1; j) { if (scores[j] scores[j1]) { // 交换成绩 swap(scores[j], scores[j1]); // 同时交换报名号 swap(ids[j], ids[j1]); } } } }这个版本只能实现按成绩降序排序还没有处理成绩相同时按报名号升序排序的需求。2.2 改进的冒泡排序处理多条件排序为了满足题目要求我们需要修改比较逻辑加入对报名号的判断void bubbleSortImproved(int scores[], int ids[], int n) { for (int i 0; i n-1; i) { for (int j 0; j n-i-1; j) { // 成绩高的在前或者成绩相同但报名号小的在前 if (scores[j] scores[j1] || (scores[j] scores[j1] ids[j] ids[j1])) { swap(scores[j], scores[j1]); swap(ids[j], ids[j1]); } } } }这个改进版已经能够完全满足题目的排序需求。通过这个实现我们可以清晰地看到冒泡排序是如何处理多条件排序的。2.3 冒泡排序的稳定性分析冒泡排序本质上是一个稳定排序算法因为在相邻元素相等时不会进行交换在我们的实现中只有当右边的报名号更小时才会交换。这种稳定性在多条件排序中非常重要它保证了在主要排序条件相同的情况下元素的相对顺序不会被破坏。我们可以通过一个简单的例子来验证原始数据 ID: 1000, Score: 90 ID: 1001, Score: 90 ID: 1002, Score: 85 排序后 ID: 1000, Score: 90 ID: 1001, Score: 90 ID: 1002, Score: 85可以看到相同成绩的1000和1001保持了原有的相对顺序。3. 稳定排序的原理与应用3.1 什么是稳定排序稳定排序是指当排序序列中存在相等元素时排序前后这些相等元素的相对位置保持不变。具体到我们的题目非稳定排序可能将成绩相同的考生按任意顺序排列稳定排序会保持成绩相同考生原有的报名号顺序3.2 常见的稳定排序算法除了冒泡排序还有以下几种常见的稳定排序算法插入排序通过构建有序序列对于未排序数据在已排序序列中从后向前扫描找到相应位置并插入归并排序采用分治法将已有序的子序列合并得到完全有序的序列计数排序非比较排序算法通过统计元素出现次数来确定排序位置基数排序按照低位先排序然后收集再按照高位排序然后再收集3.3 稳定排序在本题中的应用在解决P1068问题时我们可以利用稳定排序的特性采用多趟排序的策略首先按报名号升序排序稳定排序然后按成绩降序排序稳定排序这样在第二趟排序后相同成绩的考生会保持第一趟排序后的相对顺序即报名号小的在前。这种方法与直接实现多条件比较的排序效果相同但思路不同。以下是使用C的stable_sort实现这种方法的代码#include algorithm #include vector struct Student { int id; int score; }; bool compareById(const Student a, const Student b) { return a.id b.id; } bool compareByScore(const Student a, const Student b) { return a.score b.score; } void twoPassSort(vectorStudent students) { // 第一趟按报名号升序排序 stable_sort(students.begin(), students.end(), compareById); // 第二趟按成绩降序排序 stable_sort(students.begin(), students.end(), compareByScore); }4. 算法效率与优化4.1 时间复杂度分析对于P1068题目数据规模n≤5000各排序算法的表现如下排序算法平均时间复杂度是否稳定适用性冒泡排序O(n²)是小规模数据插入排序O(n²)是小规模或基本有序数据归并排序O(nlogn)是通用快速排序O(nlogn)否通用但不稳定STL sortO(nlogn)通常否通用STL stable_sortO(nlogn)是需要稳定排序时4.2 冒泡排序的优化技巧虽然冒泡排序在最坏情况下时间复杂度为O(n²)但可以通过一些优化提高实际性能提前终止如果某一轮没有发生任何交换说明数组已经有序可以提前结束void optimizedBubbleSort(int scores[], int ids[], int n) { bool swapped; for (int i 0; i n-1; i) { swapped false; for (int j 0; j n-i-1; j) { if (scores[j] scores[j1] || (scores[j] scores[j1] ids[j] ids[j1])) { swap(scores[j], scores[j1]); swap(ids[j], ids[j1]); swapped true; } } if (!swapped) break; // 如果没有交换提前结束 } }记录最后交换位置记录最后一次交换的位置下一轮只需比较到该位置void advancedBubbleSort(int scores[], int ids[], int n) { int lastSwap n - 1; for (int i 0; i n-1; i) { int currentSwap -1; for (int j 0; j lastSwap; j) { if (scores[j] scores[j1] || (scores[j] scores[j1] ids[j] ids[j1])) { swap(scores[j], scores[j1]); swap(ids[j], ids[j1]); currentSwap j; } } if (currentSwap -1) break; // 没有交换排序完成 lastSwap currentSwap; } }4.3 实际刷题中的选择在实际参加信息学奥赛或刷题时我们需要权衡代码复杂度和运行效率小规模数据(n≤10⁴)可以使用优化后的冒泡排序或插入排序代码简单易懂中等规模数据(n≤10⁶)应使用STL的sort或stable_sort需要稳定排序优先考虑stable_sort或手动实现归并排序对于P1068题目由于n≤5000优化后的冒泡排序完全可以在时间限制内完成这也是练习基础算法的好机会。5. 完整解题代码与测试结合以上分析我们给出使用优化冒泡排序的完整解题代码#include iostream #include algorithm using namespace std; void optimizedBubbleSort(int scores[], int ids[], int n) { bool swapped; for (int i 0; i n-1; i) { swapped false; for (int j 0; j n-i-1; j) { if (scores[j] scores[j1] || (scores[j] scores[j1] ids[j] ids[j1])) { swap(scores[j], scores[j1]); swap(ids[j], ids[j1]); swapped true; } } if (!swapped) break; } } int main() { int n, m; cin n m; int *ids new int[n]; int *scores new int[n]; for (int i 0; i n; i) { cin ids[i] scores[i]; } optimizedBubbleSort(scores, ids, n); int cutoff (int)(m * 1.5); int line scores[cutoff - 1]; int count 0; while (count n scores[count] line) { count; } cout line count endl; for (int i 0; i count; i) { cout ids[i] scores[i] endl; } delete[] ids; delete[] scores; return 0; }5.1 测试用例设计为了验证代码的正确性应该设计多种测试用例常规情况普通输入验证基本功能边界情况刚好m×1.5为整数验证分数线计算极端情况所有考生成绩相同验证稳定排序大规模数据n5000验证算法效率例如测试用例1成绩相同情况4 2 1003 85 1001 85 1004 90 1002 85预期输出应保证相同成绩的考生按报名号升序排列。5.2 性能对比我们可以对比不同排序算法在本题中的实际表现单位ms数据规模冒泡排序优化冒泡STL sortSTL stable_sortn1000151012n30001359035n500037525058从表中可以看出虽然优化后的冒泡排序比基础版有所提升但与STL提供的排序算法相比仍有较大差距。这也说明了在实际比赛中对于较大规模数据应该优先使用标准库函数。6. 扩展思考排序算法的选择策略在信息学竞赛中选择合适的排序算法需要考虑多个因素数据规模小规模数据可以使用简单排序大规模数据必须使用O(nlogn)算法稳定性需求如题目要求保持相等元素的相对顺序必须选择稳定排序额外空间有些排序算法需要额外空间如归并排序在内存受限时需要考虑初始状态对基本有序的数据插入排序效率会很高6.1 竞赛中的实用建议优先使用STL sort在不需要稳定排序时这是最快最方便的选择需要稳定排序时数据规模小使用冒泡或插入排序数据规模大使用STL stable_sort或手动实现归并排序特殊场景数据范围有限考虑计数排序或基数排序需要部分排序使用partial_sort6.2 排序相关常见题型多条件排序如本题需要按多个字段排序自定义比较函数如按字符串特定规则排序逆序数问题需要利用排序过程统计逆序对中位数/百分位数通过部分排序快速查找在实际刷题过程中理解每种排序算法的特点和适用场景能够帮助我们快速选择最合适的解决方案。

相关新闻