用C++ STL sort函数搞定合影排序题(信息学奥赛OpenJudge NOI 1.10 07)
用C++ STL sort函数实现多条件合影排序:从信息学奥赛真题到工程实践
在信息学竞赛和实际开发中,排序是最基础却最考验编程功底的算法之一。OpenJudge NOI 1.10第7题"合影效果"看似简单,却完美展现了如何用C++标准库的sort函数解决复杂排序问题。这道题要求将男生按身高升序、女生按身高降序排列,最终形成男生在前、女生在后且各自有序的队列。本文将深入剖析如何通过自定义比较规则,用STL的sort函数优雅解决这类多条件排序问题。
1. 理解问题本质与排序需求
合影排序问题的核心在于处理多重排序条件。我们需要同时考虑性别优先级(男生在前)和不同性别的排序方向(男生升序、女生降序)。这种需求在实际开发中非常常见,比如电商平台需要先按商品类别分组,再按价格或销量排序。
传统解法可能将数据分成两个数组分别排序,但这样会带来额外的内存开销和代码复杂度。更优雅的方式是定义一个统一的比较规则,这正是STL sort函数的强大之处。sort函数的时间复杂度为O(nlogn),对于NOI题目中常见的n≤1e5数据规模完全够用。
提示:在算法竞赛中,理解题目隐含的排序规则比立即编码更重要。建议先用纸笔列出几个测试用例,明确输入输出关系。
2. STL sort函数的核心机制
C++标准库中的sort函数位于 <algorithm> 头文件中,其基本用法如下:
template< class RandomIt, class Compare >
void sort( RandomIt first, RandomIt last, Compare comp );
其中 comp 是比较函数对象,它决定了元素的排序规则。当不提供comp参数时,默认使用 operator< 进行升序排序。
2.1 比较函数的实现方式
实现自定义比较有三种主流方式:
-
普通函数 :适用于简单比较逻辑
bool cmp(const Student& a, const Student& b) { // 比较逻辑 } -
函数对象(仿函数) :适合需要保持状态的比较
struct Comparator { bool operator()(const Student& a, const Student& b) { // 比较逻辑 } }; -
重载运算符 :最面向对象的方式
struct Student { bool operator<(const Student& other) const { // 比较逻辑 } };
对于合影问题,这三种方式各有优劣。我们将在下一节具体分析如何选择。
3. 解决合影问题的三种STL实现
3.1 方案一:分离数组法
这是最直观的解法,将男女数据分开存储并分别排序:
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<double> males, females;
int n;
cin >> n;
while (n--) {
string gender;
double height;
cin >> gender >> height;
if (gender == "male") {
males.push_back(height);
} else {
females.push_back(height);
}
}
// 男生升序
sort(males.begin(), males.end());
// 女生降序
sort(females.rbegin(), females.rend());
cout << fixed << setprecision(2);
for (auto h : males) cout << h << " ";
for (auto h : females) cout << h << " ";
return 0;
}
优点 :
- 逻辑简单直接
- 易于理解和调试
缺点 :
- 需要额外内存存储两个数组
- 输出时需要两次循环
3.2 方案二:自定义比较函数
更优雅的解法是使用单一数组配合自定义比较函数:
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <vector>
using namespace std;
struct Person {
string gender;
double height;
};
bool compare(const Person& a, const Person& b) {
if (a.gender != b.gender) {
return a.gender == "male"; // 男生排前面
}
return a.gender == "male" ? a.height < b.height : a.height > b.height;
}
int main() {
int n;
cin >> n;
vector<Person> people(n);
for (auto& p : people) {
cin >> p.gender >> p.height;
}
sort(people.begin(), people.end(), compare);
cout << fixed << setprecision(2);
for (const auto& p : people) {
cout << p.height << " ";
}
return 0;
}
关键点 :
- 性别不同时,男生优先
- 同性时,男生升序、女生降序
3.3 方案三:运算符重载
面向对象风格的最佳实践是重载 operator< :
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <vector>
using namespace std;
struct Person {
string gender;
double height;
bool operator<(const Person& other) const {
if (gender != other.gender) {
return gender == "male";
}
return gender == "male" ? height < other.height : height > other.height;
}
};
int main() {
int n;
cin >> n;
vector<Person> people(n);
for (auto& p : people) {
cin >> p.gender >> p.height;
}
sort(people.begin(), people.end());
cout << fixed << setprecision(2);
for (const auto& p : people) {
cout << p.height << " ";
}
return 0;
}
优势 :
- 代码更简洁
- 比较逻辑与数据结构绑定
- 可以自然用于其他需要比较的场景
4. 工程实践中的扩展应用
合影排序问题虽然简单,但其解决方案可以扩展到许多实际场景。以下是几个典型案例:
4.1 多条件排序的通用模式
当需要按多个字段排序时,可以遵循以下模式:
bool compare(const Item& a, const Item& b) {
if (a.field1 != b.field1) {
return a.field1 < b.field1; // 第一优先级
}
if (a.field2 != b.field2) {
return a.field2 > b.field2; // 第二优先级
}
return a.field3 < b.field3; // 第三优先级
}
4.2 性能优化技巧
对于大型数据集,可以考虑以下优化:
-
移动语义 :确保数据结构支持移动构造
struct Person { string gender; double height; // 添加移动构造函数 Person(Person&&) = default; }; -
缓存友好 :将频繁比较的字段放在结构体开头
struct Person { char gender[6]; // 改为固定数组避免堆分配 double height; }; -
并行排序 :对于超大数据集
#include <execution> sort(execution::par, people.begin(), people.end());
4.3 常见陷阱与调试技巧
陷阱1 :比较函数不符合严格弱序
// 错误示例:不满足传递性
bool cmp(int a, int b) {
return abs(a) <= abs(b);
}
陷阱2 :修改正在排序的元素
vector<Person> people;
// 错误:在比较函数中访问people会导致未定义行为
sort(people.begin(), people.end(), [&](auto& a, auto& b) {
return people.size() > 0 && a.height < b.height;
});
调试技巧 :
- 在比较函数中添加日志
- 使用
std::is_sorted验证结果 - 对小数据集进行单步调试
5. 从算法竞赛到实际工程
信息学奥赛题目往往提炼自实际问题。合影排序问题可以延伸至以下工程场景:
- 电商商品排序 :先按品类,再按价格/销量
- 任务调度系统 :先按优先级,再按截止时间
- 数据分析报表 :多维度数据透视排序
一个真实案例是为在线教育平台实现课程排序:
struct Course {
bool isPremium;
double rating;
int studentCount;
Date publishDate;
bool operator<(const Course& other) const {
if (isPremium != other.isPremium)
return isPremium;
if (rating != other.rating)
return rating > other.rating;
if (studentCount != other.studentCount)
return studentCount > other.studentCount;
return publishDate > other.publishDate;
}
};
在工程实践中,还需要考虑:
- 排序稳定性(stable_sort)
- 异常安全性
- 多线程环境下的同步
- 内存占用与缓存效率
更多推荐
所有评论(0)