:第十五课《结构体、排序与自定义比较——从“给数字排队”到“给一群人排队”》)
第十五课 结构体、排序与自定义比较——从“给数字排队”到“给一群人排队”一、本课学习目标学完这一课大家应该能够回答什么是结构体为什么需要结构体struct里面可以放什么.和-有什么区别为什么结构体不能像普通数字一样直接比较sort()到底在比较什么什么是“排序规则”如何实现“先按成绩高低再按姓名排序”operator 到底是什么意思CSP-J程序阅读中如何手算结构体排序二、先给同学们讲一个故事假设老师手里有三个学生小明 95分 10岁 小红 98分 11岁 小刚 90分 10岁如果只用变量int score1 95; int score2 98; int score3 90;只能保存成绩。但是一个学生实际上有很多信息姓名 年龄 成绩 学号 班级怎么办我们可以把这些信息打包在一起。这就是结构体struct三、结构体就是“信息大礼包”例如struct Student { string name; int age; int score; };这句话的意思是我们设计了一种新的数据类型叫Student。一个Student里面有三个成员Student ├── name ├── age └── score四、创建结构体变量定义struct Student { string name; int age; int score; };然后Student a;现在a ├── name ├── age └── score给它赋值a.name 小明; a.age 10; a.score 95;五、.到底是什么这是结构体最重要的符号之一a.name a.age a.score.可以理解成“从这个结构体里面找到它的某个成员。”例如a.score就是找到a的score。给大家一个非常简单的口诀对象用点找成员。六、把结构体想成一个抽屉柜例如a ┌───────────────┐ │ name │ │ 小明 │ ├───────────────┤ │ age │ │ 10 │ ├───────────────┤ │ score │ │ 95 │ └───────────────┘那么a.name就是打开第一个抽屉。a.score就是打开第三个抽屉。七、结构体数组如果有100个学生Student a[100];那么a[0] a[1] a[2] ... a[99]每一个都是一个完整的Student。例如a[0].name 小明; a[0].score 95; a[1].name 小红; a[1].score 98;这时候可以把它想象成a[0] → 小明 10岁 95 a[1] → 小红 11岁 98 a[2] → 小刚 10岁 90这就是“数组 结构体”非常重要。八、为什么结构体数组特别有用以前int score[100]; string name[100]; int age[100];三个数组必须保证score[i] name[i] age[i]永远对应同一个人。很容易乱。使用结构体Student a[100];就变成a[i] ├── name ├── age └── score一个学生的信息永远绑在一起。所以结构体解决的是“多个相关信息如何打包”。九、举个的结构体例子struct stu{ char name[100]; double score; };然后定义对象stu s;再strcpy(s.name,ZhangSan); s.score 99.99;最后输出cout s.name s.score;这体现了定义结构体 ↓ 定义一个对象 ↓ 对象里面有多个成员变量十、结构体指针例如Student a; Student *p a;此时p ↓ ap里面保存的是a的地址。十一、.和-如果是普通结构体变量Student a; a.score;使用.如果是结构体指针Student *p a;使用-所以p-score等价于(*p).score我们给出这一组对应关系stu1-name stu1-score以及(*stu1).name (*stu1).score十二、给大家一个超级简单的口诀变量用点指针用箭头。即a.score↓普通变量而p-score↓指针十三、现在进入本课的重点排序假设小明 95 小红 98 小刚 90如果按照成绩从高到低小红 98 小明 95 小刚 90如果只有数字sort(a, an);很好理解。因为90 95 98十四、但结构体怎么办现在Student a[3];里面是小明 95 小红 98 小刚 90如果直接sort(a, a3);问题来了C凭什么知道“哪个学生应该排在前面”因为一个学生不是一个数字。十五、这就是“排序规则”我们必须告诉计算机两个Student比较时到底比较什么例如比较成绩那么score大的排前面或者score小的排前面甚至成绩高的优先 如果成绩一样年龄小的优先 如果年龄也一样姓名字典序小的优先这就是自定义排序规则十六、第一种写法比较函数最适合小学生理解的是bool cmp(Student a, Student b) { return a.score b.score; }然后sort(a, an, cmp);意思如果a.score b.score那么a应该排在b前面。十七、cmp到底在干什么这是孩子最容易困惑的地方。不要把return a.score b.score;理解成“比较之后返回成绩”。它实际上是在回答一个问题“a应该排在b前面吗”如果a 小明 95 b 小红 98那么a.score b.score是95 98结果false意思小明不应该排在小红前面。十八、换一个比较如果a 小红 98 b 小明 95那么a.score b.score是98 95结果true意思小红应该排在小明前面。所以bool cmp(Student a, Student b) { return a.score b.score; }就是成绩从大到小排序。十九、为什么升序是如果bool cmp(Student a, Student b) { return a.score b.score; }那么意思变成成绩小的应该排在前面。因此 → 升序 → 降序但是一定要强调这不是死记“就是升序”。真正应该记住cmp(a,b)回答的是“a是否应该排在b前面”二十、这是本课最重要的一句话请让孩子记住sort不是在问“谁大”而是在问“谁排前面”。例如return a.score b.score;并不是“a比b大吗”而是“a的成绩比b高所以a应该排在b前面吗”这一个理解非常重要。二十一、多关键字排序这是CSP-J非常值得掌握的内容。例如成绩越高越前 成绩相同学号越小越前写bool cmp(Student a, Student b) { if(a.score ! b.score) return a.score b.score; return a.id b.id; }二十二、为什么要写两个条件例如小明 95 3 小红 98 5 小刚 95 1第一关键字score先按照成绩98 95 95于是小红 小明 小刚小明和小刚成绩一样。怎么办继续比较id因为1 3所以小红 小刚 小明二十三、多关键字排序的通用模板大家可以记成bool cmp(Student a, Student b) { if(第一关键字不同) return 第一关键字的排序规则; return 第二关键字的排序规则; }如果有三个关键字bool cmp(Student a, Student b) { if(a.score ! b.score) return a.score b.score; if(a.age ! b.age) return a.age b.age; return a.name b.name; }就是第一关键字 ↓ 相同 ↓ 第二关键字 ↓ 还相同 ↓ 第三关键字二十四、给同学们一个“比赛排名”的方法假设比赛排名规则第一 分数高 第二 分数一样时用时短 第三 分数、用时都一样时编号小那么bool cmp(Player a, Player b) { if(a.score ! b.score) return a.score b.score; if(a.time ! b.time) return a.time b.time; return a.id b.id; }这其实就是“先比第一项平手再比第二项再平手再比第三项。”二十五、这和字典排序很像字符串abc abd比较第一字符 ↓ 相同 ↓ 第二字符 ↓ 相同 ↓ 第三字符结构体多关键字排序也是第一关键字 ↓ 相同 ↓ 第二关键字 ↓ 相同 ↓ 第三关键字所以大家会发现排序其实就是一层一层地比较。二十六、operator是什么现在进入稍微提高一点的内容。有时候我们会看到struct Student { int score; bool operator (const Student b) const { return score b.score; } };孩子第一反应通常是老师这么长到底在干什么其实它就是在告诉C“两个Student使用时应该怎么比较。”二十七、普通数字的例如3 5C知道true因为整数本身就有大小关系。但是Student a,b; a b;C不知道Student到底怎么比较于是我们自己定义bool operator (...)二十八、把它理解成“教C一条新规则”原来C知道3 5现在我们告诉它Student a Student b是什么意思。例如bool operator (const Student b) const { return score b.score; }就是告诉C两个学生比较大小时看成绩。于是sort(a,an);就可以工作了。二十九、为什么名字叫operator因为operator 合起来operator就是“小于号运算符的自定义版本”。同理C还可以定义operator operator operator等等。本课对于CSP-J初赛理解它“重新定义比较规则”的思想即可。不必再深入探究运算符重载的底层实现。三十、结构体排序程序阅读题现在开始真正进入CSP-J模式。看程序#include bits/stdc.h using namespace std; struct Student { string name; int score; }; bool cmp(Student a, Student b) { return a.score b.score; } int main() { Student a[3] { {A, 80}, {B, 95}, {C, 90} }; sort(a, a3, cmp); for(int i0;i3;i) cout a[i].name ; }问输出什么三十一、第一步先写原始数据A 80 B 95 C 90三十二、第二步读cmpreturn a.score b.score;说明成绩高的在前面。三十三、第三步排序95 → B 90 → C 80 → A所以B C A三十四、第四步看输出cout a[i].name ;所以B C A这就是完整程序阅读过程。三十五、再来一道多关键字题struct Student { int score; int id; }; bool cmp(Student a, Student b) { if(a.score ! b.score) return a.score b.score; return a.id b.id; }数据A90 3 B95 2 C90 1 D95 5三十六、先看第一关键字成绩B 95 D 95 A 90 C 90三十七、成绩相同怎么办95分B id2 D id5因为2 5所以B D90分A id3 C id1因为1 3所以C A最终B D C A三十八、初赛容易错的地方错误1看到就认为是“降序”这个说法不够准确。真正应该看return a.score b.score;它回答a应该排在b前面吗所以这里才得到分数高的排前错误2忘记“相同再比较”看到if(a.score ! b.score) return a.score b.score; return a.id b.id;一定要翻译成先成绩 ↓ 成绩相同 ↓ 再编号错误3把结构体当成一个数字例如Student a,b;不能简单认为ab到底是什么意思必须找到cmp或者operator看它定义的规则。三十九、sort()本身不负责决定规则这是非常值得孩子理解的一个思想。sort(a,an,cmp);可以分成sort ↓ 负责“排序” cmp ↓ 负责“告诉它谁在前面”也就是说sort是裁判cmp是比赛规则。这是一个非常好的类比。四十、如果没有cmp呢例如sort(a,an);它会使用默认的“小于比较”。如果结构体自己定义了operator那么就可以使用。所以sort(a,an,cmp)和sort(a,an)可以理解为第一种sort 我们专门提供的比较规则第二种sort 结构体自己定义的规则四十一、结构体排序与普通排序的知识升级大家已经会sort(a,an);排序3 1 5 2现在继续升级成sort(Student数组)排序姓名 成绩 年龄 学号 时间这意味着从“给数据排序”升级到“给对象排序”。这是很重要的编程思维。四十二、本课和时间复杂度连接起来冒泡排序 O(n²) 选择排序 O(n²) 快速排序 O(n log n)所以看到sort(a,an);初赛中除了问排完是什么还可能问时间复杂度大约是多少对于C标准库sort我们可以把它理解为O(n log n)因此冒泡 O(n²) sort O(n log n)当n很大时差别非常明显。四十三、为什么排序会出现在很多算法里这其实是下一阶段算法思想的重要入口。例如原数据 ↓ 排序 ↓ 变得有规律 ↓ 更容易处理比如7 2 9 4 1排序1 2 4 7 9排序以后找最大值更方便找最小值更方便找第K大更方便二分查找可以开始使用贪心算法经常需要排序区间问题经常需要排序所以排序不仅仅是“把数字排整齐”。它是很多算法的前置工具。四十四、本课要掌握的程序阅读方法以后看到struct ...第一步把每个成员写出来。看到Student a[5];第二步把它理解成5个完整的Student。看到a[i].score第三步找到第i个学生的成绩。看到sort(...)第四步找比较规则。看到cmp(...)第五步把cmp翻译成“谁应该排前面”。看到if(...)第六步先比较第一关键字平手再比较第二关键字。四十五、综合案例CSP-J初赛类型的题。#include bits/stdc.h using namespace std; struct Student { string name; int score; int id; }; bool cmp(Student a, Student b) { if(a.score ! b.score) return a.score b.score; return a.id b.id; } int main() { Student a[4] { {Tom,90,3}, {Jack,95,4}, {Lucy,90,1}, {Bob,95,2} }; sort(a,a4,cmp); for(int i0;i4;i) cout a[i].name ; }四十六、不要直接看答案先画表姓名分数学号Tom903Jack954Lucy901Bob952比较规则第一关键字score 大的在前 第二关键字id 小的在前于是第一组95分Jack id4 Bob id2学号小的Bob Jack第二组90分Tom id3 Lucy id1学号小的Lucy Tom所以答案Bob Jack Lucy Tom四十七、总结一张“排序地图”sort │ ┌───────┴────────┐ ↓ ↓ 普通数组 结构体数组 │ │ 3 1 5 2 ↓ │ cmp ↓ │ 默认比较 ↓ 谁应该排前面 │ ┌──────┼──────┐ ↓ ↓ ↓ 第一 第二 第三 关键字 关键字 关键字 │ ↓ 排序四十八、本课“必背10句话”1.struct可以把多个相关信息打包在一起。2. 一个结构体变量里面可以有多个成员。3. 普通结构体变量使用.访问成员。4. 结构体指针使用-访问成员。5.p-x等价于(*p).x。6. 结构体数组就是很多个完整的结构体排成一队。7.sort()负责排序比较规则决定谁排前面。8.cmp(a,b)回答的是“a应该排在b前面吗”9. 多关键字排序就是“第一关键字相同再比较第二关键字”。10. 看到operator要想到“这个结构体自己定义了小于号的比较规则”。四十九、课后练习本课重点训练程序阅读能力。1、基础题给struct Student { string name; int score; };让大家讲解a.name a.score a[0].name a[1].score分别表示什么。2、提高题给bool cmp(Student a, Student b) { return a.score b.score; }让大家讲解成绩是从大到小还是从小到大3、综合题给if(a.score ! b.score) return a.score b.score; return a.id b.id;让大家讲解第一关键字是什么 第二关键字是什么 分别升序还是降序4、最后一题给一组5个学生姓名、成绩、学号先手工排出最终结果再书写完整程序。