用C++解决‘合影效果’排序题:从STL sort到冒泡排序的三种实战写法(附避坑点)
用C++解决‘合影效果’排序题:从STL sort到冒泡排序的三种实战写法(附避坑点)
在算法竞赛中,排序问题是最基础也最常遇到的题型之一。"合影效果"这道题目看似简单,却蕴含了多种C++排序技巧的实战应用场景。本文将深入探讨三种不同的实现方案:STL sort标准库调用、运算符重载的面向对象写法,以及最基础的冒泡排序实现。每种方法都有其独特的编码思路和适用场景,特别适合正在准备NOI/NOIP等竞赛的初中级选手学习掌握。
1. 问题分析与基础解法
合影效果问题的核心在于按照特定规则对学生进行排序:所有男生需要按身高升序排列,所有女生需要按身高降序排列,并且男生要排在女生前面。题目给出的数据规模通常很小(n≤40),因此算法的时间复杂度不是主要考量因素,重点在于正确实现排序逻辑。
1.1 分离数组法
最直观的解法是将男生和女生的身高数据分别存储在两个不同的数组中,然后分别进行排序处理:
#include <bits/stdc++.h> using namespace std; bool cmpDown(double a, double b) { return a > b; } int main() { char s[10]; double a, male[45], female[45]; int n, mi = 0, fi = 0; cin >> n; for(int i = 0; i < n; ++i) { cin >> s >> a; if(s[0] == 'm') male[mi++] = a; else female[fi++] = a; } sort(male, male + mi); // 男生升序 sort(female, female + fi, cmpDown); // 女生降序 for(int i = 0; i < mi; ++i) cout << fixed << setprecision(2) << male[i] << ' '; for(int i = 0; i < fi; ++i) cout << fixed << setprecision(2) << female[i] << ' '; return 0; }关键点说明:
- 使用两个独立数组分别存储男女身高数据
sort函数的第三个参数可以传入自定义比较函数- 输出时使用
fixed和setprecision(2)保证小数点后两位
注意:数组下标从0开始还是从1开始是初学者常见的困惑点,建议统一使用从0开始的规范,这与STL容器的设计一致。
1.2 常见错误分析
在实际编码中,容易出现以下几种典型错误:
- 比较函数逻辑错误:女生排序误用升序而非降序
- 输出格式问题:忘记设置浮点数输出精度
- 数组越界:错误处理数组边界条件
- 输入处理错误:性别判断只检查首字母但未考虑空字符串情况
2. 进阶解法:统一排序条件
虽然分离数组法直观易懂,但在实际工程中,我们更倾向于使用统一的排序条件来处理数据。这种方法的核心是设计一个能够同时处理三种情况的比较规则:
- 当比较对象性别不同时,男生排在前面
- 当都是男生时,身高低的排在前面
- 当都是女生时,身高高的排在前面
2.1 运算符重载实现
C++的运算符重载特性非常适合这种场景:
struct Student { string gender; double height; bool operator<(const Student& other) const { if(gender == "male") { if(other.gender == "male") return height < other.height; // 男生升序 else return true; // 男生排女生前 } else { if(other.gender == "female") return height > other.height; // 女生降序 else return false; // 女生排男生后 } } };使用示例:
vector<Student> students; // ... 输入数据 ... sort(students.begin(), students.end());优势分析:
- 代码逻辑集中,便于维护
- 符合面向对象设计原则
- 可以直接用于STL算法
2.2 Lambda表达式实现
C++11引入的lambda表达式提供了另一种灵活的实现方式:
sort(students.begin(), students.end(), [](const Student& a, const Student& b) { if(a.gender != b.gender) return a.gender == "male"; else if(a.gender == "male") return a.height < b.height; else return a.height > b.height; });性能考虑: 虽然lambda表达式写法简洁,但在极端性能敏感场景下,函数对象可能具有更好的性能表现。
3. 基础算法实现:冒泡排序
理解底层排序算法对于算法学习至关重要。我们来看看如何用冒泡排序实现这个特定排序需求:
void bubbleSort(Student arr[], int n) { for(int i = 0; i < n-1; ++i) { for(int j = 0; j < n-i-1; ++j) { bool shouldSwap = false; if(arr[j].gender != arr[j+1].gender) { shouldSwap = arr[j+1].gender == "male"; } else if(arr[j].gender == "male") { shouldSwap = arr[j].height > arr[j+1].height; } else { shouldSwap = arr[j].height < arr[j+1].height; } if(shouldSwap) { swap(arr[j], arr[j+1]); } } } }教学价值:
- 帮助学生理解排序算法的核心思想
- 演示如何将复杂比较逻辑融入基础算法
- 展示算法优化的可能性(如加入提前终止判断)
4. 性能对比与选型建议
虽然题目数据规模小,各种方法性能差异不大,但了解不同实现的特点对实际开发很有帮助:
| 实现方式 | 代码复杂度 | 可读性 | 可维护性 | 性能 |
|---|---|---|---|---|
| 分离数组法 | 低 | 高 | 中 | 高 |
| 运算符重载 | 中 | 高 | 高 | 高 |
| Lambda表达式 | 中 | 中 | 中 | 中 |
| 冒泡排序 | 高 | 低 | 低 | 低 |
选型建议:
- 竞赛快速解题:分离数组法或Lambda表达式
- 工程代码开发:运算符重载
- 教学演示:冒泡排序实现
5. 深入理解比较函数
比较函数是排序算法的核心,需要满足严格弱序(Strict Weak Ordering)的要求:
- 非自反性:
comp(x, x)必须为false - 非对称性:如果
comp(x, y)为true,则comp(y, x)必须为false - 传递性:如果
comp(x, y)和comp(y, z)都为true,那么comp(x, z)也必须为true
我们的合影问题比较逻辑完全符合这些要求,这也是能够正确排序的前提条件。
6. 输入输出优化
在算法竞赛中,输入输出效率有时会成为瓶颈。对于C++,有以下优化技巧:
// 关闭同步,提升cin/cout速度 ios::sync_with_stdio(false); cin.tie(nullptr); // 使用更快的输入方式 char gender[10]; double height; scanf("%s %lf", gender, &height); // 输出优化 printf("%.2f ", height);注意事项:
- 关闭同步后,不要混用C和C++风格的IO
printf比cout通常更快,但类型安全性较低- 在C++17及以上版本中,可以考虑使用
std::from_chars进行更高效的输入解析
