1. 编程练习解析:东华OJ基础题112-114
作为一名计算机专业的学生,刷OJ题是提升编程能力的必经之路。最近我在准备东华大学的复试,整理了112-114三道基础题的解题思路和代码实现。这三道题涵盖了字符统计、学生成绩处理和结构体排序等常见编程考点,非常适合用来巩固C++基础。
1.1 基础112:字符频度统计
这道题要求我们统计输入文本中各个字母出现的频率,并按频率从高到低输出,频率相同时按字母顺序输出。这是典型的哈希表应用场景,但用数组也能高效解决。
核心思路是:
- 创建一个包含26个元素的结构体数组,对应26个字母
- 遍历输入字符串,统计每个字母出现的次数
- 对统计结果进行排序,先按次数降序,次数相同按字母升序
- 输出非零统计结果
cpp复制#include <bits/stdc++.h>
using namespace std;
struct Ch {
int seq; // 字母序号(0-25对应A-Z)
int count; // 出现次数
};
int main() {
string line;
while(getline(cin, line)) {
Ch chs[26] = {};
// 初始化字母序号
for(int i = 0; i < 26; i++) {
chs[i].seq = i;
chs[i].count = 0;
}
// 统计字母出现次数
for(char c : line) {
if(c >= 'A' && c <= 'Z') chs[c-'A'].count++;
else if(c >= 'a' && c <= 'z') chs[c-'a'].count++;
}
// 冒泡排序:按次数降序,次数相同按字母升序
for(int i = 0; i < 25; i++) {
for(int j = 0; j < 25-i; j++) {
if(chs[j].count < chs[j+1].count ||
(chs[j].count == chs[j+1].count && chs[j].seq > chs[j+1].seq)) {
swap(chs[j], chs[j+1]);
}
}
}
// 输出结果
for(int i = 0; i < 26; i++) {
if(chs[i].count != 0) {
cout << char('A' + chs[i].seq) << " " << chs[i].count << "\n";
}
}
cout << "\n";
}
return 0;
}
注意事项:
- 字母大小写不敏感,统一转为大写或小写处理
- 冒泡排序中交换条件的写法是关键,要同时考虑次数和字母顺序
- 输出时注意跳过出现次数为0的字母
1.2 基础113:学生成绩处理
这道题要求处理5个学生的数据,计算每个学生的平均成绩,并找出数学成绩最高的学生。
输入格式为:姓名 数学成绩 政治成绩(空格分隔)
输出要求:
- 每行输出学生姓名和平均成绩
- 最后输出数学成绩最高的学生姓名和成绩
cpp复制#include <bits/stdc++.h>
using namespace std;
struct Student {
string name;
int math = 0;
int zz = 0; // 政治成绩
};
int main() {
Student stu[5];
int num = 0;
// 读取5个学生的数据
while(num < 5) {
string line;
getline(cin, line);
int change = 0; // 字段切换标记
for(char ch : line) {
if(ch == ' ') {
change++;
continue;
}
if(change == 0) {
stu[num].name += ch; // 拼接姓名
} else if(change == 1) {
stu[num].math = stu[num].math * 10 + (ch - '0'); // 计算数学成绩
} else if(change == 2) {
stu[num].zz = stu[num].zz * 10 + (ch - '0'); // 计算政治成绩
}
}
num++;
}
// 计算平均成绩并找出数学最高分
int max_math = 0;
int max_index = 0;
for(int i = 0; i < 5; i++) {
int avg = (stu[i].math + stu[i].zz) / 2;
cout << stu[i].name << " " << avg << endl;
if(stu[i].math > max_math) {
max_math = stu[i].math;
max_index = i;
}
}
// 输出数学成绩最高的学生
cout << stu[max_index].name << " " << stu[max_index].math
<< " " << stu[max_index].zz << endl;
return 0;
}
实操心得:
- 使用结构体存储学生信息更清晰
- 成绩读取时要注意字符转数字的技巧(ch - '0')
- 处理字符串输入时,空格分隔的字段可以用状态变量处理
- 找最大值时记录下标比记录值更方便
1.3 基础114:学生信息排序
这道题要求输入N个学生的完整信息(编号、姓名、性别、年龄、成绩),按成绩从低到高排序后输出。
cpp复制#include <bits/stdc++.h>
using namespace std;
struct Student {
int id = 0;
string name;
int sex = 0; // 0:male, 1:female
int age = 0;
int score = 0;
};
int main() {
int n;
cin >> n;
getchar(); // 吸收换行符
Student stu[10]; // 题目保证N≤10
int index = 0;
while(n--) {
string line;
getline(cin, line);
int status = 0; // 字段状态
int value = 0;
string name;
for(char ch : line) {
if(ch == ' ') {
// 根据状态存储当前值
switch(status) {
case 0: stu[index].id = value; break;
case 1: stu[index].name = name; break;
case 2: stu[index].sex = value; break;
case 3: stu[index].age = value; break;
case 4: stu[index].score = value; break;
}
value = 0;
status++;
continue;
}
// 根据状态处理当前字符
if(status == 0) {
value = value * 10 + (ch - '0'); // ID
} else if(status == 1) {
name += ch; // 姓名
} else if(status == 2) {
if(ch == 'f') value = 1; // 性别(f->1, m->0)
} else if(status == 3) {
value = value * 10 + (ch - '0'); // 年龄
} else if(status == 4) {
value = value * 10 + (ch - '0'); // 成绩
}
}
stu[index].score = value; // 存储最后一个字段
index++;
}
// 冒泡排序:按成绩升序
for(int i = 0; i < index-1; i++) {
for(int j = 0; j < index-1-i; j++) {
if(stu[j+1].score < stu[j].score) {
swap(stu[j], stu[j+1]);
}
}
}
// 输出结果
for(int i = 0; i < index; i++) {
cout << stu[i].id << " " << stu[i].name << " ";
cout << (stu[i].sex ? "female" : "male") << " ";
cout << stu[i].age << " " << stu[i].score << "\n";
}
return 0;
}
常见问题:
- 输入时要注意吸收换行符,避免getline读取空行
- 性别字段处理要明确约定(如f对应1,其他为0)
- 最后一个字段后没有空格,需要单独处理
- 排序算法要确保稳定(题目保证成绩唯一,可不考虑稳定性)
2. 解题技巧总结
2.1 输入处理技巧
这三道题都涉及到从标准输入读取数据,但输入格式各不相同。总结几个常用技巧:
- 混合使用cin和getline时,记得用getchar()吸收换行符
- 空格分隔的字段可以用状态变量处理,遇到空格切换状态
- 字符转数字的技巧:value = value * 10 + (ch - '0')
- 字符串拼接:直接使用+=操作符最方便
2.2 结构体使用
结构体是组织相关数据的利器,使用时注意:
- 合理设计结构体成员,反映问题域中的实体属性
- 可以在定义时给成员赋默认值
- 结构体数组适合存储多个同类对象
- 排序时可以直接交换整个结构体,保持数据一致性
2.3 排序算法选择
虽然题目中使用了冒泡排序,但在实际编程中:
- 数据量小时冒泡排序足够用,且代码简单
- 数据量大时应使用更高效的排序(如快速排序)
- C++中可以直接使用sort函数配合自定义比较函数
- 多关键字排序要明确优先级
3. 代码优化建议
3.1 使用标准库函数
现代C++提供了丰富的算法库,可以简化代码:
cpp复制// 替代手写冒泡排序
sort(chs, chs+26, [](const Ch& a, const Ch& b) {
return a.count > b.count || (a.count == b.count && a.seq < b.seq);
});
// 使用accumulate计算平均值
int avg = accumulate(begin(scores), end(scores), 0) / scores.size();
3.2 输入验证
增加输入验证使程序更健壮:
cpp复制// 检查学生数量是否合法
if(n < 1 || n > 10) {
cerr << "学生数量应在1-10之间" << endl;
return 1;
}
// 检查成绩是否合法
if(score < 0 || score > 100) {
cerr << "成绩应在0-100之间" << endl;
continue;
}
3.3 使用更合适的数据结构
根据问题特点选择数据结构:
- 字符统计可以用unordered_map
- 学生信息可以用vector动态扩容
- 查找最高分可以用priority_queue
4. 扩展练习建议
掌握了这三道基础题后,可以尝试以下扩展练习:
- 修改112题,支持统计所有ASCII字符而不仅是字母
- 修改113题,处理任意数量的学生而不仅是5个
- 修改114题,支持相同成绩时的次级排序(如按年龄)
- 将排序算法改为快速排序或归并排序
- 添加文件IO功能,从文件读取输入并输出到文件
在实际编程练习中,我建议先确保基础功能正确实现,再考虑优化和扩展。每完成一道题后,花时间分析时间复杂度和空间复杂度,思考是否有改进空间。同时,养成良好的代码风格和注释习惯,这对团队协作和代码维护非常重要。