用C++给班级合影排个队:从‘合影效果’这道题聊聊自定义排序的实战技巧
·
用C++给班级合影排个队:从‘合影效果’这道题聊聊自定义排序的实战技巧
班级合影时,摄影师总会喊一句:"男生站前排,女生站后排,按身高排好!"这看似简单的需求背后,却隐藏着编程中一个经典问题—— 如何实现多条件自定义排序 。今天我们就以这道来自信息学奥赛的"合影效果"题为切入点,探讨C++中灵活运用 sort 函数的艺术。
1. 理解问题本质:从现实场景到算法抽象
想象你正在组织班级毕业照拍摄,老师给了你一份名单:
张三 男 1.75
李四 女 1.68
王五 男 1.80
...
需要满足三个要求:
- 所有男生排在女生前面
- 男生之间按身高升序排列(矮在前)
- 女生之间按身高降序排列(高在前)
这实际上定义了 三个层级的排序规则 :性别优先,然后是不同性别采用不同的身高排序方式。理解这种现实需求到算法规则的映射,是解决复杂排序问题的第一步。
提示:在解决任何排序问题前,先用自然语言明确描述排序规则,这能帮助厘清思路。
2. 基础解法:分而治之的排序策略
最直观的解法是将问题拆解:
vector<double> maleHeights, femaleHeights;
// 数据收集...
sort(maleHeights.begin(), maleHeights.end()); // 男生升序
sort(femaleHeights.rbegin(), femaleHeights.rend()); // 女生降序
优点 :
- 逻辑简单直接
- 适合初学者理解
- 对小规模数据效率足够
缺点 :
- 需要维护多个容器
- 当排序条件增多时代码会急剧膨胀
- 不适用于需要统一处理的情况
下表对比了分治策略与统一排序的适用场景:
| 场景 | 分治排序 | 统一排序 |
|---|---|---|
| 条件简单 | ✓ 更直观 | ✓ 代码更紧凑 |
| 条件复杂 | ✗ 维护困难 | ✓ 逻辑集中 |
| 数据量大 | ✗ 多次排序 | ✓ 单次处理 |
| 需要扩展 | ✗ 修改困难 | ✓ 易于调整 |
3. 进阶技巧:自定义比较函数的艺术
C++的 sort 函数强大之处在于支持自定义比较规则。我们来看三种实现方式:
3.1 比较函数版本
bool compareStudents(const Student& a, const Student& b) {
if (a.gender != b.gender)
return a.gender == "male"; // 男在前
if (a.gender == "male")
return a.height < b.height; // 男升序
return a.height > b.height; // 女降序
}
3.2 运算符重载版本
struct Student {
string gender;
double height;
bool operator<(const Student& other) const {
if (gender != other.gender)
return gender == "male";
if (gender == "male")
return height < other.height;
return height > other.height;
}
};
3.3 Lambda表达式版本(C++11+)
sort(students.begin(), students.end(), [](const auto& a, const auto& b) {
if (a.gender != b.gender)
return a.gender == "male";
return a.gender == "male" ? a.height < b.height : a.height > b.height;
});
性能考虑 :
- 现代编译器对这三种方式的优化效果相当
- Lambda版本通常能生成最紧凑的代码
- 运算符重载使类型更自包含
4. 实战扩展:从合影到复杂排序场景
掌握了自定义排序的核心思想后,我们可以将其应用到更广泛的场景:
4.1 电商商品排序
假设需要实现"先按评分降序,评分相同按价格升序,价格相同按销量降序":
sort(products.begin(), products.end(), [](const auto& a, const auto& b) {
if (a.rating != b.rating) return a.rating > b.rating;
if (a.price != b.price) return a.price < b.price;
return a.sales > b.sales;
});
4.2 游戏排行榜
"先按等级降序,等级相同按经验值降序,最后按注册时间升序(老玩家优先)":
sort(players.begin(), players.end(), [](const auto& a, const auto& b) {
if (a.level != b.level) return a.level > b.level;
if (a.exp != b.exp) return a.exp > b.exp;
return a.registerTime < b.registerTime;
});
4.3 多语言字符串排序
考虑中文拼音、英文大小写不敏感的混合排序:
bool compareStrings(const string& a, const string& b) {
// 自定义中文拼音比较逻辑
// 忽略英文大小写比较
// 混合处理...
}
5. 性能优化与陷阱规避
当处理大规模数据时,排序性能变得至关重要。以下是几个实用技巧:
优化策略 :
- 优先比较计算成本低的字段
- 考虑使用
stable_sort当需要保持相等元素的原始顺序 - 对固定排序规则,预计算排序键
常见陷阱 :
-
比较函数必须满足严格弱序:
comp(a,a)必须为false- 如果
comp(a,b)为true,则comp(b,a)必须为false - 可传递性要求
-
浮点数比较需特殊处理:
bool compareDouble(double a, double b) { const double eps = 1e-9; if (fabs(a - b) < eps) return false; // 视为相等 return a < b; } -
避免在比较函数中修改元素:
- 比较函数应该是"纯函数"
- 修改元素会导致未定义行为
6. C++17/20新特性应用
现代C++提供了更多排序工具:
6.1 结构化绑定简化访问
sort(students.begin(), students.end(), [](const auto& a, const auto& b) {
auto [a_gender, a_height] = a;
auto [b_gender, b_height] = b;
// 比较逻辑...
});
6.2 三路比较运算符(C++20)
struct Student {
string gender;
double height;
auto operator<=>(const Student&) const = default;
// 自动生成所有比较运算符
};
6.3 并行排序(C++17)
#include <execution>
sort(std::execution::par, students.begin(), students.end());
在实际项目中,我发现当数据量超过10万条时,并行排序能带来2-4倍的性能提升,特别是在多核处理器上。不过要注意并行算法对比较函数的线程安全要求更高。
更多推荐

所有评论(0)