news 2026/10/23 11:18:25

体系 2 · 排序与结构体 讲义

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
体系 2 · 排序与结构体 讲义

结构体 + 排序是普及组的必考核心地基:多关键字排序、数据分组排序、复杂对象排序、贪心前的预处理,几乎都建立在它之上。

学习目标
  • 掌握结构体定义、赋值、结构体数组
  • 掌握 sort 默认排序与自定义 cmp
  • 掌握单 / 多关键字优先级排序(真题高频)
  • 能独立完成结构体排序类真题

PART 01

一、结构体

定义与访问

结构体(struct)把不同类型、但属于同一个对象的数据打包成一个整体。

struct Student { int id; // 学号 int score; // 分数 string name; // 姓名 }; // ← 末尾分号必须写! Student s1; // 单个变量 s1.score = 95; // 用 “对象.字段” 访问 Student a[105]; // 结构体数组(刷题最常用) cin >> a[1].id >> a[1].score;

✅ 记忆要点

算法题几乎都用结构体数组批量存对象;访问字段一律 对象.字段,数组大小按范围开足。

PART 02

二、sort 排序函数

默认排序与 cmp

头文件(万能头已包含);区间左闭右开 [l, r)。#include <algorithm> sort(起始地址, 结束地址, 比较函数);

不写比较函数时默认升序:sort(a,a+n);

💡 cmp 核心口诀

cmp(x,y) 返回 true,表示 x 应排在 y 前面。

// 降序 bool cmpInt(int x,int y){ return x > y; } // 结构体多关键字:先分数降序,再学号升序 bool cmp(Student x, Student y) { if (x.score != y.score) return x.score > y.score; // 第一关键字 return x.id < y.id; // 第二关键字 }

⚠️ 严格弱序

用 >/<,不要用 >=/<=;两对象相等时必须返回 false,否则 sort 行为未定义。

多关键字万能法:按优先级从高到低,本关键字不等就返回其结果,相等才进入下一级。

PART 03

三、万能代码模板

#include <bits/stdc++.h> using namespace std; struct Node { int x, y; }; Node a[100005]; bool cmp(Node a, Node b) { if (a.x != b.x) return a.x > b.x; // 优先级1:x 降序 return a.y < b.y; // 优先级2:y 升序 } int main() { int n; cin >> n; for (int i=1;i<=n;i++) cin >> a[i].x >> a[i].y; sort(a+1, a+1+n, cmp); for (int i=1;i<=n;i++) cout << a[i].x << " " << a[i].y << '\n'; }

PART 04

四、39 题精讲(分析 + 折叠代码)

按“基础数组排序 → 单关键字/贪心 → 多关键字 → 综合进阶”分 4 组。先自己写,再展开代码对照。

A 组 · 基础数组排序

sort / 计数 / 去极值 11 题

sort 模板P1177 【模板】排序

把 n 个数升序输出。

直接 sort 即可,注意数据可能为负、范围较大,用 int 足够。

查看代码

int n; cin >> n; vector<int> a(n); for (int &x:a) cin>>x; sort(a.begin(), a.end()); for (int x:a) cout << x << " ";

去重排序P1059 [NOIP 2006 普及组] 明明的随机数

去重后升序,输出个数与序列。

排序后只在“与前一个不同”时输出;个数即不同值数量。

查看代码

sort(a+1, a+1+n); int m=0; for (int i=1;i<=n;i++) if (i==1 || a[i]!=a[i-1]) b[++m]=a[i]; cout << m << '\n'; for (int i=1;i<=m;i++) cout << b[i] << " ";

计数排序P1271 【深基9.例1】选举学生会

统计每个候选人得票并按编号输出。

票号即候选人编号(范围 n),用计数数组统计,再从小到大按次数输出。

查看代码

int n, m, x; cin >> n >> m; vector<int> cnt(n+1,0); for (int i=0;i<m;i++){ cin>>x; cnt[x]++; } for (int i=1;i<=n;i++) for (int j=0;j<cnt[i];j++) cout << i << " ";

逆序对P1116 车厢重组

求把车厢排成升序所需相邻交换次数。

相邻交换最少次数 =逆序对个数。双重循环统计 i<j 且 a[i]>a[j] 的对数(n 很小)。

查看代码

long long ans=0; for (int i=1;i<=n;i++) for (int j=i+1;j<=n;j++) if (a[i] > a[j]) ans++;

排序取值B2158 谁考了第 k 名

成绩降序,输出第 k 名分数(成绩互不相同)。

降序排序后取 a[k](或 a[k-1],按下标)。

查看代码

sort(a+1, a+1+n, [int](int x,int y){ return x>y; }); cout << a[k];

找最大B2125 最高分数的学生姓名

输出最高分学生姓名。

边读边维护最高分与对应姓名;最高分唯一。

查看代码

string name, ansName; int mx=-1, score; for (int i=0;i<n;i++){ cin >> name >> score; if (score > mx) { mx=score; ansName=name; } }

稳定排序B2159 成绩排序

成绩从高到低,同分按输入先后(稳定)。

用结构体存“分数 + 原下标”,cmp 中分数相等按下标升序,即保证稳定。

查看代码

struct S{ int score, id; string name; }; bool cmp(S a,S b){ if(a.score!=b.score) return a.score>b.score; return a.id<b.id; }

结构体P5744 【深基7.习9】培训

学员年龄 +1,改名为编号,再按年龄降序输出。

结构体存姓名、年龄、原编号;处理后按年龄降序(同龄按编号)排序输出。

查看代码

struct S{ string name; int age, id; }; for (int i=0;i<n;i++){ cin >> a[i].name >> a[i].age; a[i].age++; a[i].id=i; a[i].name=to_string(i); } sort(a,a+n,[](S x,S y){ if(x.age!=y.age)return x.age>y.age; return x.id<y.id; });

去极值P5738 【深基7.例4】歌唱比赛

去掉一个最高、一个最低分后求平均。

对每位评委的分数排序,去掉首尾后求和,再除以 (m-2)。

查看代码

double mx=0; for (int i=0;i<n;i++){ vector<int> s(m); for(int&x:s)cin>>x; sort(s.begin(),s.end()); long long sum=0; for(int j=1;j<m-1;j++) sum+=s[j]; mx=max(mx,(double)sum/(m-2)); }

多字段排序B3968 [GESP202403 五级] 成绩排序

按总分降序,同分语文降序,再同则学号升序。

典型三关键字 cmp,按优先级逐级判断。

查看代码

bool cmp(S a,S b){ if(a.total!=b.total) return a.total>b.total; if(a.chinese!=b.chinese) return a.chinese>b.chinese; return a.id<b.id; }

排序统计P2681 众数

求出现次数最多的数(次数相同取较小值)。

排序后扫描连续相同段,维护最大次数;次数相同取数值小者(排序后先出现即小)。

查看代码

B 组 · 单关键字与贪心

结构体 + 一个核心属性排序 10 题

贪心P1223 排队接水

按接水时间升序,使平均等待最小。

短作业优先。第 i 人(0 起)的时间被后面 n-1-i 人等待,贡献 t[i]*(n-1-i)。

查看代码

struct P{int t,id;}; bool cmp(P a,P b){ if(a.t!=b.t)return a.t<b.t; return a.id<b.id; } sort(v.begin(),v.end(),cmp); long long total=0; for(int i=0;i<n;i++){ cout<<v[i].id<<" "; total+=(long long)v[i].t*(n-1-i); } cout<<fixed<<setprecision(2)<<(double)total/n;

双指针贪心P1094 [NOIP 2007 普及组] 纪念品分组

每组最多 2 件、总价 ≤ W,求最少组数。

排序后首尾配对:能凑就两件一起,否则最贵单独。

查看代码

sort(a,a+n); int i=0,j=n-1,ans=0; while(i<=j){ if(i==j){ans++;break;} if(a[i]+a[j]<=W)i++; j--;ans++; }

排序贪心P1478 陶陶摘苹果(升级版)

够得到的苹果按体力升序摘,求最多个数。

可达高度 a+b,先筛高度,再按消耗体力升序贪心。

查看代码

int reach=a+b; vector<int> v; for(int i=0;i<n;i++){ int x,c;cin>>x>>c; if(x<=reach)v.push_back(c); } sort(v.begin(),v.end()); int ans=0; for(int x:v)if(s>=x){s-=x;ans++;}else break;

两端贪心P4995 跳跳!

在最高/最低石头间交替跳,最大化高度差平方和。

从地面 0 起,先跳最高、再跳剩余最低,两端交替,累加落差平方(long long)。

查看代码

sort(a.begin(),a.end()); int l=0,r=n-1; long long now=0,ans=0; while(l<=r){ ans+=(a[r]-now)*(a[r]-now);now=a[r];r--; if(l>r)break; ans+=(now-a[l])*(now-a[l]);now=a[l];l++; }

降序贪心P2676 [USACO07DEC] Bookshelf B

牛身高降序,累加到不低于书架高度。

降序排序后依次累加,直到总和 ≥ B。

查看代码

sort(a,a+n,[](int x,int y){return x>y;}); long long sum=0; for(int i=0;i<n;i++){sum+=a[i];if(sum>=B){cout<<i+1;break;}}

排序贪心P2695 骑士的工作

龙头与骑士能力都升序,用最小够用的骑士砍头。

两个数组都排序,双指针:能力够砍当前头就计数,否则换更强骑士。

查看代码

sort(head,head+n); sort(knight,knight+m); int i=0,j=0,cnt=0; long long cost=0; while(i<n && j<m){ if(knight[j]>=head[i]){cost+=knight[j];cnt++;i++;j++;} else j++; } if(cnt==n)cout<<cost;else cout<<"you died!";

高度排序P5143 攀爬者

按海拔升序爬山,求总路程(相邻高度差之和)。

按高度升序排序,累加相邻两点高度差。

查看代码

sort(a,a+n); double ans=0; for(int i=1;i<n;i++) ans+=a[i]-a[i-1];

中位数P1862 输油管道问题

主管道东西走向,选 y 使纵向支线总长最小。

绝对值距离之和在中位数处最小。对所有 y 排序取中位,累加 |y-中位|。

查看代码

sort(y,y+n); int mid=y[n/2]; long long ans=0; for(int i=0;i<n;i++) ans+=abs(y[i]-mid);

价格贪心P1208 [USACO1.3] 混合牛奶 Mixing Milk

按单价升序购买,凑够需求量使花费最小。

结构体存单价、数量,按单价升序,逐个购买直到满足需求。

查看代码

struct M{int price, amount;}; sort(milk,milk+n,[](M a,M b){return a.price<b.price;}); long long cost=0; int need=total; for(int i=0;i<n && need>0;i++){ int buy=min(need,milk[i].amount); cost+=(long long)buy*milk[i].price; need-=buy; }

区间贪心P2859 [USACO06FEB] Stall Reservations S

求最少畜栏数,并给出每头牛的栏号。

按开始时间升序,用小根堆维护各栏当前结束时间:最早栏能复用(结束<当前开始)就复用并更新,否则新开栏;记录分配的栏号。

查看代码

struct Cow{int l,r,id;}; sort(a,a+n,[](Cow x,Cow y){return x.l<y.l;}); priority_queue<pair<int,int>, vector<pair<int,int>>, greater<>> pq; for(int i=0;i<n;i++){ if(!pq.empty() && pq.top().first<a[i].l){ ans[a[i].id]=pq.top().second;pq.pop(); }else ans[a[i].id]=++stalls; pq.push({a[i].r,ans[a[i].id]}); }

C 组 · 多关键字排序

优先级逐级兜底 13 题

三关键字P1093 [NOIP 2007 普及组] 奖学金

总分降序、语文降序、学号升序。

结构体存三科与总分,三关键字 cmp。

查看代码

bool cmp(S a,S b){ if(a.total!=b.total)return a.total>b.total; if(a.chi!=b.chi)return a.chi>b.chi; return a.id<b.id; }

分数线+排序P1068 [NOIP 2009 普及组] 分数线划定

按 plan×1.5 取面试名额,再按笔试降序、编号升序。

先按笔试降序排,取第 ⌊m×1.5⌋ 名成绩为线,所有 ≥ 线者进面(同分都进);输出时保持降序、同编号升序。

查看代码

sort(a,a+n,[](S x,S y){ if(x.score!=y.score)return x.score>y.score; return x.id<y.id; }); int line=a[(int)(m*1.5)-1].score; for(int i=0;i<n;i++) if(a[i].score>=line) cout<<a[i].id<<" "<<a[i].score<<'\n';

日期多关键字P1104 生日

按年、月、日升序,同年同月同日先输入的在前。

结构体存年月日与原序,cmp 逐级升序,全等按原序。

查看代码

bool cmp(S a,S 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.id<b.id; }

分组排序B2160 病人排队

≥60 岁按年龄降序(同龄按登记序),其余按登记序。

分成“老人”和“普通”两组:老人按年龄降序、登记序排前面;普通组保登记序,接在后面。

查看代码

struct S{string id;int age,ord;}; vector<S> old, normal; for(int i=0;i<n;i++){ if(a[i].age>=60)old.push_back(a[i]); else normal.push_back(a[i]); } sort(old.begin(),old.end(),[](S x,S y){ if(x.age!=y.age)return x.age>y.age; return x.ord<y.ord; });

取第一P5740 【深基7.例9】最厉害的学生

总分降序、学号升序,输出第一名三科。

算总分后排序,或直接维护“最优学生”,输出其信息。

查看代码

int bestId=1; for(int i=1;i<=n;i++){ cin>>a[i].c1>>a[i].c2>>a[i].c3; a[i].total=a[i].c1+a[i].c2+a[i].c3; if(a[i].total>a[bestId].total)bestId=i; }

两两判断P5741 【深基7.例10】旗鼓相当的对手 - 加强版

两学生每科分差绝对值都 ≤5 且总分不等即互为对手。

双重循环枚举两人,三科分差全 ≤5 且 total 不等则互相输出名字。

查看代码

for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++){ if(abs(a[i].c-a[j].c)<=5&&abs(a[i].m-a[j].m)<=5 &&abs(a[i].e-a[j].e)<=5&&a[i].total!=a[j].total) cout<<a[i].name<<" "<<a[j].name<<'\n'; }

条件评级P5742 【深基7.例11】评等级

综合分=德育×0.7+智育×0.3;满足条件评优秀。

按题意计算综合分(可用整数放大避免浮点误差),逐条判断输出。

查看代码

for(int i=0;i<n;i++){ int id,c1,c2;cin>>id>>c1>>c2; int score=7*c1+3*c2; // 放大10倍,>=800 即综合≥80 if(c1+c2>140 && score>=800) cout<<"Excellent\n"; else cout<<"Not excellent\n"; }

多规则累加P1051 [NOIP 2005 提高组] 谁拿了最多奖学金

按 5 条规则累加奖学金,求最大与总和。

奖金可叠加:期末>80且论文≥1加8000;期末>85且评议>80加4000;期末>90加2000;西部且期末>85加1000;干部且评议>80加850。

查看代码

if(s.final_>80&&s.paper>=1)money+=8000; if(s.final_>85&&s.cls_>80)money+=4000; if(s.final_>90)money+=2000; if(s.west=="Y"&&s.final_>85)money+=1000; if(s.lead=="Y"&&s.cls_>80)money+=850;

归并优化P1309 [NOIP 2011 普及组] 瑞士轮

每轮相邻对战,胜/负组各自有序,归并合并。

每轮整体 sort 超时。赢家、输家内部仍有序,用 merge 归并成新序列 O(N)。

查看代码

while(R--){ vector<P> win,lose; for(int i=0;i<2*N;i+=2){ if(p[i].w>p[i+1].w){p[i].score++;win.push_back(p[i]);lose.push_back(p[i+1]);} else{p[i+1].score++;win.push_back(p[i+1]);lose.push_back(p[i]);} } merge(win.begin(),win.end(),lose.begin(),lose.end(),p.begin(),cmp); }

计数不重排P7910 [CSP-J 2021] 插入排序

稳定排序后问 x 的名次;O(n) 数前面元素,不真排序。

排在 x 前的 = 值更小的 + 值相等但下标更小的。每次查询 O(n) 计数。

查看代码

int rank=0; for(int i=1;i<=n;i++){ if(a[i]<a[x])rank++; else if(a[i]==a[x]&&i<=x)rank++; }

频次差判素P1125 [NOIP 2008 提高组] 笨小猴

字母最大/最小出现次数之差判素数。

统计 26 字母频次(只看出现过的),d=max-min;d 为素数则 Lucky Word。

查看代码

int mx=0,mn=1000; for(int x:cnt)if(x){mx=max(mx,x);mn=min(mn,x);} int d=mx-mn; bool ok=d>=2; for(int i=2;i*i<=d;i++)if(d%i==0)ok=false;

多组排序P1056 [NOIP 2008 普及组] 排座椅

分别对行、列通道排序取前 k 后升序输出。

统计每对相邻行/列被多少对同学隔开,各自降序取前 k,再把选中编号升序输出。

查看代码

// row[i]:第 i、i+1 行间被隔开的对数;col 同理 sort(pickRow.begin(),pickRow.end(),greater<int>()); sort(pickCol.begin(),pickCol.end(),greater<int>()); vector<int> R(pickRow.begin(),pickRow.begin()+k); vector<int> C(pickCol.begin(),pickCol.begin()+l); sort(R.begin(),R.end()); sort(C.begin(),C.end());

中位数P1889 [CEOI 1998] 士兵站队

x、y 分别移动到连续整列,最小化总步数。

y 方向取中位数;x 先升序排序,令 b[i]=x[i]-i 化为“对齐同一点”,再取 b 的中位数。两方向相加。

查看代码

sort(x,x+n); sort(y,y+n); for(int i=0;i<n;i++) b[i]=x[i]-i; sort(b,b+n); int my=y[n/2], mx=b[n/2], ans=0; for(int i=0;i<n;i++){ans+=abs(y[i]-my);ans+=abs(b[i]-mx);}

D 组 · 综合与进阶

排序为预处理,主考其他算法 5 题

字符串贪心P1200 [USACO1.1] 你的飞碟在这儿

把彗星名/小组名各字母乘积 mod 47,相等则 GO。

字母 A=1…Z=26,分别累乘取模 47,比较两结果。

查看代码

auto code=[](string s){ int p=1; for(char c:s)p=p*(c-'A'+1)%47; return p; }; cout<<(code(comet)==code(group)?"GO":"STAY");

拓扑DPP1113 [USACO02FEB] 杂务

杂务有前置依赖,求完成全部的最短时间。

每项最早完成时间 = 自身耗时 + max(各前置的完成时间)。按输入顺序(已是拓扑序)递推,最后取所有完成时间最大值。

查看代码

int ans=0; for(int i=1;i<=n;i++){ int id,t,pre,earliest=0; cin>>id>>t>>pre; while(pre){earliest=max(earliest,f[pre]);cin>>pre;} f[id]=earliest+t; ans=max(ans,f[id]); }

日期枚举P2010 [NOIP 2016 普及组] 回文日期

由年份逆序构造 8 位回文日期并校验合法。

对每个 4 位年份 y,把其数字逆序作后 4 位得 yyyymmdd;校验月日合法(含闰年),落在区间内则计数。

查看代码

auto rev4=[](int y){return y%10*1000+y/10%10*100+y/100%10*10+y/1000;}; int md=rev4(y),m=md/100,d=md%100; if(m<1||m>12||d<1||d>dayLimit(y,m))continue;

字符串cmpP1012 [NOIP 1998 提高组] 拼数

把若干数拼成最大的数。

以字符串存储,cmp 按拼接结果比较:return a+b > b+a; 排序后依次拼接。

查看代码

vector<string> s(n); sort(s.begin(),s.end(),[](string a,string b){return a+b>b+a;}); for(string& x:s)cout<<x;

快速选择P1923 【深基9.例4】求第 k 小的数

n 很大,整体排序可能偏慢,用快速选择 O(n)。

可用 nth_element(a,a+k,a+n) 直接把第 k 小放到第 k 位(平均 O(n));手写则用快排划分、只递归含 k 的一侧。

查看代码

// C++ 最简 nth_element(a,a+k,a+n); cout<<a[k];

PART 05

五、总结与自主练习

结构体 + 排序的核心套路只有三步:先定义结构体把每个对象的多个属性打包,再写 cmp 比较函数按优先级从高到低逐级判断、相等才进入下一级并补上兜底返回,最后调用 sort传入起始地址和 cmp 即可完成排序。只要严格遵循“多关键字逐级判断 + 严格弱序(相等返回 false)”,任何排序题都能稳定拿下。

下面 5 道题从 A/B/C 组中精选,覆盖基础、贪心与多关键字三大考点,建议先独立完成,再展开折叠代码对照,重点检查自己的 cmp 是否写全、升降序是否与题意一致:

  • P1177 排序(A 组):sort 模板题,练手基础排序与输入输出,注意数据可能为负。
  • P1059 明明的随机数(A 组):排序 + 去重,练习“只在与前一个不同时输出”的经典写法。
  • P1223 排队接水(B 组):单关键字贪心,体会“短作业优先”如何让平均等待最小。
  • P1094 纪念品分组(B 组):排序 + 双指针贪心,练习首尾配对的最优策略。
  • P1093 奖学金(C 组):三关键字排序,总分降序、语文降序、学号升序,直接套用万能 cmp 模板。

六、易错点与自查清单

结构体 / cmp 易错
  • 结构体定义末尾漏分号;
  • cmp 用 >=/<=,相等返回 true,破坏严格弱序;
  • 多关键字顺序写反,必须先高后低;
  • 漏写完全相等时的兜底返回;
sort / 范围易错
  • 区间搞错:排 1~n 写 sort(a+1,a+1+n);
  • 数组开太小 RE;该用 long long 用了 int;
  • 该稳定排序没保序;
  • 多轮整体重 sort 超时(P1309 归并);

上考场前自查清单

  • ✅ 字段类型选对了吗?用数组还是结构体数组?
  • ✅ 各关键字及升降序写全了吗?cmp 对相等返回 false 吗?
  • ✅ sort 起止地址对吗?数据从 0 还是 1 开始?
  • ✅ 看过数据范围吗?数组够大吗?要不要 long long / 字符串存大数?
  • ✅ 有没有多轮重排?要归并 / 计数 / 快选吗?
  • ✅ 这题主考点真是排序吗?还是贪心 / 二分 / 分治 / 模拟 / 拓扑?
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 5:50:27

Redis缓存与数据库一致性实战:方案选型与踩坑总结

1. 缓存命中率上去了&#xff0c;数据却乱了&#xff1a;一致性问题的本质来源我记得第一次在生产环境把Redis缓存铺到订单详情页的时候&#xff0c;效果立竿见影——接口平均耗时从380ms掉到40ms&#xff0c;DB主库的QPS从2万降到3000。正当我沾沾自喜的时候&#xff0c;客服那…

作者头像 李华
网站建设 2026/10/21 5:50:27

RSuite TimeRangePicker 占位符(Placeholder)详解:从用法到源码原理

前端UI组件 【免费下载链接】rsuite &#x1f9f1; A suite of React components . 项目地址&#xff1a; https://gitcode.com/gh_mirrors/rs/rsuite 点击查看 免费下载 导读 本文围绕 RSuite 组件库中 TimeRangePicker 时间区间选择器的 placeholder&#xff08;占位符&am…

作者头像 李华