1. 项目概述
字符串排序是编程入门阶段必须掌握的经典算法问题。东华OJ第88题要求使用C++实现对输入字符串按特定规则排序的功能,这道题目看似简单,却涵盖了字符串处理、排序算法、输入输出控制等多个基础知识点。作为计算机专业学生或编程初学者,通过这道题目的练习,可以系统性地提升对C++标准库的运用能力。
我在大学期间第一次接触这道题目时,花了整整一个下午才完全理解题意并写出正确解法。后来在担任ACM校队教练的五年里,我指导过上百名学生完成这道题目,总结出了一套高效的解题思路和常见错误规避方法。下面将分享我的完整解题经验。
2. 核心需求解析
2.1 题目要求详解
原始题目描述通常包含以下核心要求:
- 输入多组测试数据,每组包含若干字符串
- 对每组字符串按字典序升序排列
- 输出排序后的结果,每组输出间用空行分隔
实际测试案例可能如下:
code复制输入:
3
apple
Orange
banana
2
Pear
grape
输出:
Orange
apple
banana
Pear
grape
2.2 技术难点分析
这道题目看似简单,但隐藏着几个关键难点:
- 混合大小写字符串的字典序比较(ASCII码中大写字母排在小写字母前面)
- 多组输入的循环处理与终止条件判断
- 输出格式控制(组间空行、最后一行无空行)
- 内存管理(特别是使用动态数组时)
提示:很多初学者会忽略大小写敏感问题,直接使用默认的string比较,导致排序结果不符合预期。
3. 解决方案设计
3.1 算法选择
对于字符串排序问题,我们有多种算法选择:
| 算法 | 时间复杂度 | 空间复杂度 | 适用性 |
|---|---|---|---|
| 冒泡排序 | O(n²) | O(1) | 教学演示,实际不推荐 |
| 快速排序 | O(nlogn) | O(logn) | 标准库常用实现 |
| 归并排序 | O(nlogn) | O(n) | 稳定排序需求 |
| STL sort | O(nlogn) | 取决于实现 | 本题最佳选择 |
在C++中,直接使用<algorithm>中的sort函数是最优选择,因为:
- 代码简洁,只需几行即可实现
- 性能优异(通常是快速排序的优化实现)
- 可自定义比较函数处理特殊需求
3.2 核心代码结构
基础解决方案框架如下:
cpp复制#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
bool compare(const string &a, const string &b) {
// 自定义比较函数
return a < b;
}
int main() {
int n;
while (cin >> n) {
vector<string> strs(n);
for (int i = 0; i < n; ++i) {
cin >> strs[i];
}
sort(strs.begin(), strs.end(), compare);
for (const auto &s : strs) {
cout << s << endl;
}
cout << endl; // 组间空行
}
return 0;
}
4. 关键实现细节
4.1 大小写敏感处理
原始代码中的比较函数直接使用a < b,这会导致大写字母总是排在小写字母前面(因为'A'的ASCII码是65,'a'是97)。如果需要不区分大小写的排序,比较函数应修改为:
cpp复制bool compare(const string &a, const string &b) {
string lowerA, lowerB;
transform(a.begin(), a.end(), back_inserter(lowerA), ::tolower);
transform(b.begin(), b.end(), back_inserter(lowerB), ::tolower);
return lowerA < lowerB;
}
不过根据东华OJ的测试用例,通常要求区分大小写的字典序排序,所以原始比较函数已经满足要求。
4.2 输入输出优化
对于大规模输入,常规的cin/cout可能会超时。建议添加以下优化:
cpp复制ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
这可以显著提升IO速度,但需要注意:
- 此后不能混用C风格的scanf/printf
- 关闭了与C标准库的同步,线程安全性降低
4.3 内存管理技巧
使用vector自动管理内存比原始数组更安全:
cpp复制vector<string> strs(n); // 自动管理内存
// 而非
string *strs = new string[n]; // 需要手动delete[]
5. 完整实现代码
综合所有优化后的完整解决方案:
cpp复制#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n;
bool first = true;
while (cin >> n) {
if (!first) cout << endl;
first = false;
vector<string> strs(n);
for (int i = 0; i < n; ++i) {
cin >> strs[i];
}
sort(strs.begin(), strs.end());
for (const auto &s : strs) {
cout << s << endl;
}
}
return 0;
}
6. 常见问题与调试技巧
6.1 典型错误案例
-
输出格式错误:
- 多输出空行或少输出空行
- 解决方法:使用标志位控制空行输出
-
内存泄漏:
- 使用new但忘记delete
- 解决方法:优先使用vector等容器
-
排序结果异常:
- 未考虑大小写敏感问题
- 解决方法:明确题目要求,必要时自定义比较函数
6.2 调试技巧
-
边界测试:
- 空输入测试
- 单字符串测试
- 大量字符串测试
-
输出中间结果:
cpp复制// 在sort前后输出字符串数组 for (const auto &s : strs) { cerr << s << " "; } cerr << endl; -
使用assert验证:
cpp复制#include <cassert> assert(strs[0] <= strs[1]); // 验证排序结果
7. 性能优化进阶
对于超大规模数据(如10^5个字符串),可以考虑:
-
预分配内存:
cpp复制vector<string> strs; strs.reserve(n); // 预先分配足够空间 -
移动语义:
cpp复制string temp; for (int i = 0; i < n; ++i) { cin >> temp; strs.push_back(move(temp)); // 使用移动而非拷贝 } -
并行排序:
cpp复制#include <execution> sort(execution::par, strs.begin(), strs.end());
8. 扩展思考
这道基础题目可以延伸出多个变种练习:
-
稳定排序需求:
- 当字符串相同时保持原始输入顺序
- 使用stable_sort代替sort
-
多关键字排序:
- 先按长度排序,长度相同再按字典序
cpp复制bool compare(const string &a, const string &b) { if (a.length() != b.length()) return a.length() < b.length(); return a < b; } -
外部排序:
- 当数据量超过内存大小时
- 需要使用归并排序等外部排序算法
在实际工程中,字符串排序的应用场景非常广泛,比如:
- 文件系统的目录列表显示
- 数据库的ORDER BY操作
- 搜索引擎的结果排序
掌握这些基础算法和优化技巧,对后续学习更复杂的数据结构和算法有很大帮助。我在实际项目开发中,经常需要处理百万级字符串的排序问题,这时对算法复杂度的理解和性能优化经验就显得尤为重要。