#9982. 2026/7/29/区赛训练笔记(结构体+结构体排序)
2026/7/29/区赛训练笔记(结构体+结构体排序)
结构体与结构体排序课堂笔记
一、什么是结构体
结构体可以把一个对象的多项信息放在一起。
例如,一名学生有学号、姓名和成绩:
struct student{
int id;
string name;
int score;
};
定义一个学生:
student a;
访问成员时使用点号:
cin>>a.id>>a.name>>a.score;
cout<<a.id<<" "<<a.name<<" "<<a.score;
结构体数组:
student a[N];
输入第 i 名学生:
cin>>a[i].id>>a[i].name>>a[i].score;
二、为什么使用结构体
如果不用结构体,可能需要分别定义三个数组:
int id[N],score[N];
string name[N];
排序时必须保证三个数组一起移动,容易出错。
使用结构体后,一个学生的全部信息会一起交换:
sort(a+1,a+1+n,f);
因此,只要结构体成员设计清楚,代码会更短,也更安全。
三、结构体的输入和输出
1. 正序输入、正序输出
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
struct fd{
int id,score;
string name;
};
fd a[N];
int n;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].id>>a[i].name>>a[i].score;
}
for(int i=1;i<=n;i++){
cout<<a[i].id<<" "<<a[i].name<<" "<<a[i].score<<endl;
}
return 0;
}
2. 倒序输出
如果题目要求按输入顺序的倒序输出,不需要排序,只要倒序枚举:
for(int i=n;i>=1;i--){
cout<<a[i].id<<" "<<a[i].name<<" "<<a[i].score<<endl;
}
注意:倒序输出不等于排序。题目只要求把输入顺序反过来时,不需要调用 sort()。
四、结构体排序
1. sort 的基本格式
sort(a+1,a+1+n,f);
a+1:从a[1]开始。a+1+n:排序到a[n]。f:比较函数。
比较函数的形式:
bool f(fd s1,fd s2){
//s1应该排在s2前面时返回true
}
必须记住:
返回 true:s1 放在 s2 前面
返回 false:不要求 s1 放在 s2 前面
它不是在回答“二者是否相等”。
2. 单关键字排序
成绩从高到低:
bool f(fd s1,fd s2){
return s1.score>s2.score;
}
学号从小到大:
bool f(fd s1,fd s2){
return s1.id<s2.id;
}
3. 多关键字排序
例如,学生成绩排序的规则是:
- 成绩高的在前。
- 成绩相同时,学号小的在前。
bool f(fd s1,fd s2){
if(s1.score==s2.score){
return s1.id<s2.id;
}else{
return s1.score>s2.score;
}
}
可以把规则理解为:先比较第一关键字,第一关键字相同后再比较第二关键字。
4. 字符串排序
例如,姓名排序的规则是:
- 姓名长度降序。
- 长度相同,姓名字典序降序。
- 姓名相同,学号降序。
bool f(fd s1,fd s2){
int l1=s1.name.size();
int l2=s2.name.size();
if(l1!=l2) return l1>l2;
if(s1.name!=s2.name) return s1.name>s2.name;
return s1.id>s2.id;
}
写多关键字排序时,使用“不同就立刻返回”的形式更清楚。
五、比较函数的常见错误
1. 排序后能否用比较函数判断并列
如果已经使用同一个合法的比较函数完成排序,并且比较的是相邻元素,那么下面的写法可以判断两人在排序规则下是否等价:
if(f(a[i-1],a[i])==0){
//两人在比较函数使用的关键字上相同,可以并列
}
原因是排序后的相邻元素已经保证:
f(a[i],a[i-1])==0
如果此时还有 f(a[i-1],a[i])==0,就说明谁都不应该排在谁前面,因此二者在该比较规则下等价。
但要注意:这里证明的是“排序关键字等价”,不一定表示结构体的所有成员完全相同。例如两人的原输入编号 p 可以不同,但成绩关键字相同,仍然应该并列。
为了让代码含义更加直观,也可以单独定义:
bool same(fd s1,fd s2){
return s1.z==s2.z&&s1.l==s2.l&&s1.g==s2.g;
}
在本题中,两种写法都正确,same() 只是更容易阅读和维护。
2. 括号位置错误
一种常见的错误写法是:
f(s[i-1],s[i]==0)
这里会先计算:
s[i]==0
但 s[i] 是结构体,不能直接与整数 0 比较,因此编译失败。
如果确实要判断比较函数的返回值,应写:
f(s[i-1],s[i])==0
这才是原代码中的括号错误。改正后可以直接用于判断并列,也可以使用独立的 same() 函数让含义更清楚。
3. 相等时返回 true
错误示例:
if(s1.score==s2.score) return true;
两个完全相等的对象不能互相都排在对方前面。完全相等时应返回 false。
六、排名问题
1. 普通排名
如果没有并列:
第1名、第2名、第3名、第4名
排序后,第 i 个对象的排名就是 i。
2. 并列且跳号
如果题目规定多人并列时需要跳过后续名次,排名可能是:
1, 1, 3, 4, 4, 6
如果当前学生与前一名并列,排名不变;否则排名应等于当前排序位置 i:
a[1].m=1;
for(int i=2;i<=n;i++){
if(same(a[i-1],a[i])) a[i].m=a[i-1].m;
else a[i].m=i;
}
不能写成:
a[i].m=a[i-1].m+1;
因为前面如果有多人并列,下一名需要跳过被占用的名次。
3. 恢复输入顺序
题目先要求按成绩排序计算排名,最后却要求按原输入顺序输出。
输入时保存原位置:
a[i].p=i;
计算排名后:
ans[a[i].p]=a[i].m;
最后输出:
for(int i=1;i<=n;i++) cout<<ans[i]<<endl;
七、综合例题:多科成绩排序
题目规则
依次比较:
- 三科总分,高者在前。
- 语文、数学两科总分,高者在前。
- 语文、数学两科最高分,高者在前。
- 三项全部相同,则并列。
正确代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
struct fd{
int a,b,c;
int z,l,g;
int p,m;
};
bool f(fd s1,fd s2){
if(s1.z!=s2.z) return s1.z>s2.z;
if(s1.l!=s2.l) return s1.l>s2.l;
return s1.g>s2.g;
}
bool same(fd s1,fd s2){
return s1.z==s2.z&&s1.l==s2.l&&s1.g==s2.g;
}
fd s[N];
int n,ans[N];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>s[i].a>>s[i].b>>s[i].c;
s[i].z=s[i].a+s[i].b+s[i].c;//三科总分
s[i].l=s[i].a+s[i].b;//语文数学总分
s[i].g=max(s[i].a,s[i].b);//语文数学最高分
s[i].p=i;//记录原输入位置
}
sort(s+1,s+1+n,f);
s[1].m=1;
for(int i=2;i<=n;i++){
if(same(s[i-1],s[i])) s[i].m=s[i-1].m;
else s[i].m=i;//不并列时,排名等于排序位置
}
for(int i=1;i<=n;i++){
ans[s[i].p]=s[i].m;//恢复到原输入顺序
}
for(int i=1;i<=n;i++){
cout<<ans[i]<<endl;
}
return 0;
}
时间复杂度为 O(n log n),空间复杂度为 O(n)。
八、常见运行错误
1. 数组开小导致段错误
例如题目允许 n<=10000,却只定义:
fd s[105];
但题目范围是:
n<=10000
当输入人数超过 104 时会越界,容易出现 Segmentation fault。应根据范围开数组:
const int N=2e5+10;
fd s[N];
2. 程序没有输出
如果只完成排序和排名计算,却没有写最终输出循环,评测会读到 EOF。
完成代码后要检查:
输入是否读完?
算法是否执行?
答案是否输出?
输出顺序是否符合题目?
3. 排名递推错误
并列后下一名不是“上一名排名加一”,而是当前排序位置 i。
4. 排序结果和输出顺序不同
有些题目需要借助排序计算名次,但最后要求按输入顺序输出。因此必须保存 p,不能直接输出排序后的数组。
九、结构体排序通用模板
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
struct fd{
int x,y,p;
string s;
};
bool f(fd s1,fd s2){
if(s1.x!=s2.x) return s1.x>s2.x;//第一关键字降序
if(s1.y!=s2.y) return s1.y<s2.y;//第二关键字升序
return s1.p<s2.p;//最后用原编号保证顺序唯一
}
fd a[N];
int n;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].x>>a[i].y>>a[i].s;
a[i].p=i;
}
sort(a+1,a+1+n,f);
for(int i=1;i<=n;i++){
cout<<a[i].x<<" "<<a[i].y<<" "<<a[i].s<<endl;
}
return 0;
}
十、课堂总结
结构体题可以按照以下步骤完成:
- 找出每个对象有哪些信息。
- 把这些信息写进一个结构体。
- 根据数据范围正确开数组。
- 输入所有成员,必要时保存原下标。
- 把题目的排序规则逐条写进比较函数。
- 如果有并列,可以利用排好序后的比较结果判断,也可以单独写
same()。 - 排名不并列时使用当前位置
i,处理跳号。 - 按题目要求的顺序输出答案。
最重要的两句话:
比较函数定义“谁在前面”;排好序后,可以用比较关系判断排序关键字是否等价。
排序后的顺序,不一定是题目要求的最终输出顺序。