信息学奥赛刷题实战:用C++ STL sort函数搞定整数奇偶排序(附两种解法对比)
信息学奥赛刷题实战:用C++ STL sort函数搞定整数奇偶排序(附两种解法对比)
在信息学奥赛(NOI)和OpenJudge等编程竞赛中,排序算法是最基础也是最重要的技能之一。面对"整数奇偶排序"这类看似简单实则暗藏玄机的题目,如何写出既高效又优雅的代码,往往是区分新手和老手的关键。本文将带你深入探讨如何利用C++ STL中的sort函数,通过自定义比较规则来巧妙解决这类复合排序问题。
1. 理解题目需求与挑战
整数奇偶排序题目通常要求将一组整数按照特定规则排序:所有奇数排在偶数前面,奇数之间按降序排列,偶数之间按升序排列。这种复合排序条件看似简单,但在实际编码中却可能遇到各种陷阱。
常见新手误区 :
- 试图一次性完成所有排序条件
- 忽略比较函数的严格弱序要求
- 对STL sort函数的理解不够深入
让我们先看一个典型输入输出示例:
输入:1 2 3 4 5 6 7 8 9 10
输出:9 7 5 3 1 2 4 6 8 10
2. 解法一:分而治之策略
第一种思路是将奇数和偶数分开处理,分别排序后再合并。这种方法直观易懂,特别适合初学者理解。
2.1 实现步骤
- 分离奇偶数 :遍历输入数组,将奇数存入odd数组,偶数存入even数组
- 分别排序 :
- 对odd数组进行降序排序
- 对even数组进行升序排序
- 合并输出 :先输出排序后的odd数组,再输出even数组
#include <bits/stdc++.h>
using namespace std;
bool cmpDown(int a, int b) { return a > b; } // 降序比较
bool cmpUp(int a, int b) { return a < b; } // 升序比较
int main() {
int num, odd[15], even[15], oi = 0, ei = 0;
for(int i = 0; i < 10; ++i) {
cin >> num;
if(num % 2) odd[oi++] = num;
else even[ei++] = num;
}
sort(odd, odd + oi, cmpDown);
sort(even, even + ei, cmpUp);
for(int i = 0; i < oi; ++i) cout << odd[i] << ' ';
for(int i = 0; i < ei; ++i) cout << even[i] << ' ';
return 0;
}
2.2 优缺点分析
优势 :
- 逻辑清晰,易于理解和调试
- 适合教学和初学者练习
- 对排序算法的稳定性要求较低
劣势 :
- 需要额外的存储空间
- 需要进行两次排序操作
- 代码量相对较大
3. 解法二:统一排序策略
更高级的解法是设计一个统一的比较函数,一次性完成所有排序条件。这种方法更能体现对STL sort函数的深入理解。
3.1 比较函数设计
关键点在于设计一个满足严格弱序的比较函数,同时实现:
- 奇数优先于偶数
- 奇数之间降序排列
- 偶数之间升序排列
#include <bits/stdc++.h>
using namespace std;
bool cmp(int a, int b) {
bool aOdd = a % 2, bOdd = b % 2;
if(aOdd && bOdd) return a > b; // 都是奇数,降序
if(!aOdd && !bOdd) return a < b; // 都是偶数,升序
return aOdd; // 一奇一偶,奇数在前
}
int main() {
int nums[10];
for(int i = 0; i < 10; ++i) cin >> nums[i];
sort(nums, nums + 10, cmp);
for(int i = 0; i < 10; ++i) cout << nums[i] << ' ';
return 0;
}
3.2 代码优化技巧
可以将比较函数进一步简化为单行表达式:
bool cmp(int a, int b) {
return (a%2 == b%2) ? (a%2 ? a>b : a<b) : a%2;
}
注意事项 :
- 确保比较函数满足严格弱序
- 避免在比较函数中进行复杂计算
- 考虑边界情况(如负数、零值)
4. 两种解法的性能对比
虽然题目数据量较小(仅10个数字),但了解不同解法的性能特点对培养算法思维很有帮助。
| 对比项 | 分而治之法 | 统一排序法 |
|---|---|---|
| 时间复杂度 | O(2nlogn) | O(nlogn) |
| 空间复杂度 | O(n) | O(1) |
| 代码简洁性 | 中等 | 优秀 |
| 可读性 | 高 | 中等 |
| 扩展性 | 低 | 高 |
实际应用建议 :
- 教学场景:优先使用分而治之法
- 竞赛场景:推荐统一排序法
- 大型数据集:统一排序法优势明显
5. 常见错误与调试技巧
在实现这类复合排序时,容易遇到一些典型错误:
5.1 比较函数不符合严格弱序
错误的比较函数可能导致程序崩溃或排序结果异常。例如:
// 错误示例:不满足严格弱序
bool bad_cmp(int a, int b) {
if(a % 2 != b % 2) return a % 2;
return true; // 违反反对称性
}
调试技巧 :
- 使用简单测试用例验证
- 检查比较函数的三个性质:非自反性、反对称性、传递性
5.2 数组索引处理不当
初学者常犯的数组索引错误:
// 错误示例:1-based索引与0-based索引混用
for(int i = 1; i <= 10; ++i) {
cin >> nums[i]; // 可能越界
}
sort(nums + 1, nums + 11, cmp); // 长度计算错误
最佳实践 :
- 统一使用0-based索引
- 使用vector容器替代原生数组
- 添加边界检查
6. 进阶思考与扩展
掌握了基础解法后,可以进一步思考:
6.1 使用lambda表达式
C++11引入的lambda表达式可以让代码更简洁:
sort(nums, nums + 10, [](int a, int b) {
bool aOdd = a % 2, bOdd = b % 2;
return aOdd != bOdd ? aOdd : aOdd ? a > b : a < b;
});
6.2 处理更大数据量
当数据量增大时,可以考虑:
- 使用更高效的排序算法
- 并行化排序过程
- 优化比较函数的执行效率
6.3 扩展到其他复合排序场景
类似的思路可以应用于:
- 多关键字排序
- 自定义对象排序
- 混合类型数据排序
7. 实战演练与测试用例
为了确保代码的正确性,应该设计全面的测试用例:
测试用例设计 :
- 全奇数情况
- 全偶数情况
- 混合情况
- 包含负数的情况
- 包含零的情况
- 重复数字情况
- 边界值测试(最大最小值)
示例测试代码:
void test_sort() {
vector<vector<int>> test_cases = {
{1,3,5,7,9,2,4,6,8,10}, // 标准情况
{1,1,1,1,1,2,2,2,2,2}, // 重复数字
{-3,-1,0,2,4,-5,-7,6,8,10}, // 含负数
{0,0,0,0,0,0,0,0,0,0}, // 全零
{INT_MAX, INT_MIN, 0, -1, 1, 2, -2, 3, -3, 4} // 边界值
};
for(auto &tc : test_cases) {
sort(tc.begin(), tc.end(), cmp);
// 验证排序结果...
}
}
8. 竞赛中的实用技巧
在实际竞赛中,除了正确性,还需要考虑编码效率和调试便利性:
实用技巧 :
- 预先编写常用排序比较函数模板
- 使用宏定义简化代码
- 添加调试输出语句(比赛时记得删除)
- 准备常用测试用例函数
- 熟悉STL算法的各种应用场景
// 调试输出示例
#define debug(x) cerr << #x << " = " << x << endl
void debug_print(const vector<int>& v) {
cerr << "当前数组: ";
for(int num : v) cerr << num << ' ';
cerr << endl;
}
9. 性能优化深入探讨
虽然题目数据量小,但了解性能优化对培养算法思维很重要:
9.1 比较函数优化
比较函数的执行效率直接影响排序性能:
// 优化后的比较函数
bool optimized_cmp(int a, int b) {
int aMod = a % 2, bMod = b % 2;
return (aMod ^ bMod) ? aMod : aMod ? a > b : a < b;
}
9.2 算法选择策略
根据数据特点选择算法:
- 小数据量:插入排序可能更快
- 部分有序数据:考虑适应性排序算法
- 内存受限环境:选择原地排序算法
9.3 缓存友好性
考虑数据局部性原理:
- 连续内存访问更高效
- 减少分支预测失败
- 避免不必要的内存分配
10. 从题目到竞赛思维的提升
解决这类排序问题不仅是写出正确代码,更要培养竞赛思维:
思维要点 :
- 问题分解能力:将复杂条件拆解为简单规则
- STL深度应用:充分利用标准库功能
- 边界情况考虑:全面思考各种可能输入
- 性能评估意识:即使小数据量也要考虑算法效率
- 代码简洁性:在保证可读性的前提下追求简洁
在实际竞赛中,这类排序问题往往只是更大问题的组成部分。培养快速准确解决基础问题的能力,才能在面对复杂问题时游刃有余。
更多推荐


所有评论(0)