ARTICLE DETAIL

资讯详情

深耕网站SEO优化与搜索引擎排名提升的一线实战洞察。

洛谷P1104生日题解:结构体排序与自定义比较函数实践

洛谷P1104生日题解:结构体排序与自定义比较函数实践 1. 项目概述洛谷P1104生日题解这道题目来自知名在线编程题库洛谷编号P1104题目名为生日。这是一道典型的排序算法应用题主要考察学生对结构体排序和自定义比较函数的掌握程度。题目要求对一组包含姓名、年、月、日信息的生日数据进行排序输出年龄从大到小的顺序。在实际教学中这类题目经常出现在信息学竞赛的入门阶段因为它很好地结合了基础数据结构和实际生活场景。我当年刚开始学习编程时也曾经被这类题目困扰过——明明知道要用排序但就是写不对比较函数。今天我就来详细拆解这道题的解题思路和实现细节。2. 题目分析与核心思路2.1 题目要求解析题目给出n个人的信息包括姓名、出生年、月、日。要求按照年龄从大到小即出生日期从小到大的顺序输出姓名。如果有相同日期的情况则按输入顺序输出。样例输入3 Yang 1990 4 23 Li 1990 4 23 Wang 1990 12 21样例输出Wang Yang Li2.2 解题思路拆解解决这个问题的核心在于三点如何存储每个人的信息如何定义比较规则如何实现稳定排序对于存储最直观的方式是使用结构体C或类其他语言包含四个字段姓名、年、月、日。考虑到需要保留原始输入顺序还应该增加一个索引字段。比较规则的制定是关键。年龄大的先输出意味着出生日期小的在前。因此我们需要先比较年份年份小的在前如果年份相同则比较月份月份小的在前如果月份也相同则比较日期。3. 数据结构设计与实现3.1 结构体定义在C中我们可以这样定义结构体struct Person { string name; int year, month, day; int index; // 记录输入顺序 };这里特意添加了index字段用于处理出生日期相同的情况。根据题目要求相同日期时按输入顺序输出这个index就是我们的判断依据。3.2 比较函数实现自定义比较函数是本题的核心。在C中我们可以重载小于运算符或者编写比较函数bool compare(const Person a, const Person b) { if(a.year ! b.year) return a.year b.year; if(a.month ! b.month) return a.month b.month; if(a.day ! b.day) return a.day b.day; return a.index b.index; // 日期相同时后输入的排在后面 }注意最后一行当日期完全相同时我们比较index。因为题目要求按输入顺序输出而排序是稳定的所以index大的应该排在后面。提示有些初学者可能会忽略index的比较这在有相同日期的情况下会导致错误结果。这是一个常见的陷阱。4. 完整代码实现与注释4.1 主函数逻辑#include iostream #include algorithm #include vector using namespace std; struct Person { string name; int year, month, day; int index; }; bool compare(const Person a, const Person b) { if(a.year ! b.year) return a.year b.year; if(a.month ! b.month) return a.month b.month; if(a.day ! b.day) return a.day b.day; return a.index b.index; } int main() { int n; cin n; vectorPerson people(n); for(int i 0; i n; i) { cin people[i].name people[i].year people[i].month people[i].day; people[i].index i; // 记录输入顺序 } sort(people.begin(), people.end(), compare); for(const auto p : people) { cout p.name endl; } return 0; }4.2 关键点解析输入处理使用循环读取每个人的信息同时记录他们的输入顺序index排序调用使用STL的sort函数传入自定义的比较函数输出结果排序后直接按顺序输出姓名即可5. 常见问题与调试技巧5.1 典型错误分析比较函数写反把a.year b.year写成a.year b.year导致排序方向错误忽略相同日期情况没有处理日期完全相同的情况导致输出顺序不符合要求忘记记录输入顺序没有添加index字段或者忘记在输入时赋值5.2 调试建议当程序结果不符合预期时可以打印排序前后的完整信息包括index单独测试比较函数验证比较逻辑是否正确使用简单测试用例如2-3个人的数据更容易发现问题例如可以添加调试输出// 在排序后添加 for(const auto p : people) { cout p.name p.year - p.month - p.day (index: p.index ) endl; }6. 算法优化与扩展思考6.1 性能分析当前解法的时间复杂度是O(nlogn)主要由排序步骤决定。对于n≤100的数据范围这是洛谷题目的常见限制这个复杂度完全足够。如果数据量非常大比如n1e5可以考虑以下优化使用更快的排序算法如基数排序将日期转换为数字进行比较减少比较次数6.2 题目变种这道题目可以有多种变体适合作为练习按年龄从小到大排序即出生日期从大到小只考虑月日忽略年份模拟同一年内的生日排序添加性别等其他字段实现更复杂的排序规则例如如果要按年龄从小到大排序只需修改比较函数bool compare(const Person a, const Person b) { if(a.year ! b.year) return a.year b.year; if(a.month ! b.month) return a.month b.month; if(a.day ! b.day) return a.day b.day; return a.index b.index; }7. 不同语言实现对比7.1 Python实现Python中使用元组比较的特性可以简化代码n int(input()) people [] for i in range(n): parts input().split() name parts[0] y, m, d map(int, parts[1:]) people.append((y, m, d, i, name)) # 利用元组比较特性 people.sort() for p in people: print(p[4])Python的元组比较会依次比较每个元素正好符合我们的需求。注意我们把index放在日期后面这样日期相同时会自动按index排序。7.2 Java实现Java中可以使用Comparator接口class Person { String name; int year, month, day, index; } // 比较器实现 ComparatorPerson comparator (a, b) - { if(a.year ! b.year) return Integer.compare(a.year, b.year); if(a.month ! b.month) return Integer.compare(a.month, b.month); if(a.day ! b.day) return Integer.compare(a.day, b.day); return Integer.compare(a.index, b.index); }; Collections.sort(people, comparator);8. 教学建议与学习路径这道题目非常适合作为排序算法的应用案例。我建议的学习路径是先掌握基本排序算法冒泡、选择、插入理解稳定排序的概念学习结构体/类的使用练习自定义比较规则最后解决这类综合应用题对于教学者可以设计这样的练习序列基础排序练习整数数组排序结构体排序单一字段多字段排序如先按成绩再按姓名最后是这类日期排序问题在实际教学中我发现学生最容易混淆的是排序方向升序还是降序和相同元素的处理。这道题目正好可以强化这两个概念。9. 实际应用场景延伸虽然这是一道编程练习题但类似的排序需求在实际开发中很常见员工管理系统按入职日期排序学生信息系统按出生日期排序日程管理应用按事件日期排序电商系统按订单日期排序掌握这种多字段排序的技巧对日后处理各种业务逻辑都很有帮助。比如在电商系统中你可能需要先按订单状态排序再按下单时间排序最后按订单金额排序。10. 性能测试与边界情况10.1 边界测试用例好的程序应该能处理各种边界情况最小输入n1最大输入n100根据题目限制所有人生日相同年份相同只有月日不同年月相同只有日不同包含闰年2月29日的情况10.2 性能测试虽然题目数据范围不大但作为练习可以测试更大数据量// 生成100000条测试数据 vectorPerson largeData(100000); for(int i 0; i 100000; i) { largeData[i].name Person_ to_string(i); largeData[i].year 1900 rand() % 100; largeData[i].month 1 rand() % 12; largeData[i].day 1 rand() % 28; // 简化不考虑不同月份天数差异 largeData[i].index i; } sort(largeData.begin(), largeData.end(), compare);在我的测试中对10万条数据排序大约需要50msi7-9700K完全在可接受范围内。11. 代码风格与工程实践即使是简单的算法题良好的代码风格也很重要使用有意义的变量名person比p更好添加必要注释特别是比较函数的逻辑模块化设计将比较函数单独列出错误处理虽然题目保证输入有效但实际工程中应该验证输入例如改进后的代码结构struct BirthdayRecord { string name; int year; int month; int day; int inputOrder; }; bool CompareByBirthday(const BirthdayRecord a, const BirthdayRecord b) { // 实现比较逻辑 } void ProcessBirthdaySorting() { // 主逻辑 } int main() { ProcessBirthdaySorting(); return 0; }12. 其他排序方法实现除了使用标准库的sort函数我们也可以自己实现排序算法12.1 冒泡排序实现void bubbleSort(vectorPerson people) { int n people.size(); for(int i 0; i n-1; i) { for(int j 0; j n-i-1; j) { if(compare(people[j1], people[j])) { // 如果后一个应该排在前面 swap(people[j], people[j1]); } } } }虽然时间复杂度是O(n²)但对于理解排序原理很有帮助。12.2 快速排序实现int partition(vectorPerson people, int low, int high) { auto pivot people[high]; int i low - 1; for(int j low; j high; j) { if(compare(people[j], pivot)) { i; swap(people[i], people[j]); } } swap(people[i1], people[high]); return i1; } void quickSort(vectorPerson people, int low, int high) { if(low high) { int pi partition(people, low, high); quickSort(people, low, pi-1); quickSort(people, pi1, high); } }13. 输入输出优化对于大规模数据输入输出可能成为瓶颈。可以考虑使用更快的输入方法如C的scanf代替cin关闭同步流对于Cios::sync_with_stdio(false); cin.tie(nullptr);使用\n代替endl避免频繁刷新缓冲区for(const auto p : people) { cout p.name \n; }14. 测试用例设计技巧设计好的测试用例能帮助快速发现问题常规测试随机生成一些日期极端测试最早和最晚可能的日期重复测试多个相同日期顺序测试已经有序或逆序的数据闰年测试包含2月29日例如4 Alice 2000 2 29 Bob 1999 12 31 Carol 2000 2 28 Dave 2000 2 29这个测试用例包含了闰日和相同日期的情况。15. 总结与个人心得这道题目看似简单但涵盖了多个重要编程概念。我在教学中发现学生常犯的错误主要有没有正确处理相同日期的情况比较函数逻辑错误特别是多字段比较的顺序忽略了排序的稳定性要求通过这道题我总结了几个经验对于多字段排序先列出明确的比较规则再编码总是考虑边界情况特别是相等的情况添加足够的调试输出便于验证中间结果在实际编程中这类排序问题非常常见。掌握这个技能后你会发现很多业务逻辑处理起来会得心应手。比如处理学生成绩单时你可能需要先按班级排序再按总分排序最后按学号排序——这与生日排序的思路是完全一致的。
返回列表