日常生活中,我们经常需要给数据排序 (sorting)。举一个简单的例子:
在某世界一流大学,教务部想要了解学生的内卷情况,因此抽取了 6 名学生,通过他们平时卷出的学习成绩计算出他们的平均内卷点数 (Involutionary Point Average, IPA),在 Excel 上制成了如下所示的表格:
现在要给这 6 名学生的 IPA 排个序,这在 Excel 上是很好操作的,只需要:(演示的软件为 Microsoft Office 2019 for Mac,可能与 Windows 上的有区别)
知乎视频www.zhihu.com这样排序以后,不仅 IPA 的数值降序排列了,而且 IPA 和 Name 的对应关系没有搞乱。
当然,我接下来要讲的肯定不是 Excel,而是用 C++ 实现数据的排序。
案例分析 1(冒泡排序)
我们先看一个简单一点的例子:只将数据排序,而不涉及数据与「名称」之间的对应。
问题描述:
输入一组整数(共 n 个),写程序将这组数降序输出。
关于输入:
输入分为两行:第一行是待排序的整数的总个数 n,第二行是这 n 个数,以空格分隔。
关于输出:
输出共一行,即将这 n 个数由大到小排列。数与数之间以空格分隔。
输入示例:
5
3 4 2 5 1
输出示例:
5 4 3 2 1
要解决这个问题,我们很自然地想到运用循环结构。因为使数据降序输出的实质就是依次从这组数中找到最大者、次大者、第三大者……,然后按照它们的排位依次输出。显然这是一个重复寻找的过程,需要循环结构来实现。
很明显题中所给的 n 就是循环的次数,那么程序中肯定包括一句:
for
此外,为了便于输入后存储下来,构建一个数组也是有必要的。
int
然后接一个 for 循环把这组数输入数组。
这部分都容易想到,关键是排序部分的 for 循环要怎么写。也就是,在每一轮循环中,要怎么找出那个尚未输出的最大数。
显然,如果拿最大数和数组中的任何一个数去比较,最大数都将大于等于那个数。
因此,将数组中的每个数单独拿出来与数组中的所有数逐一比较,最大数「击败」(指大于等于)所比较的数的次数为 n,次大数为 n - 1,第三大数为 n - 2,……,最小的数为 1,它只能大于等于自己。(如果数据各不相同的话)
按照这个思路,我们只需要依次输出「击败」次数为 n, n - 1, n - 2, ... ,1 的数就可以了。而这需要「将数组中的每个数单独拿出来与数组中的所有数逐一比较」——也就意味着,我们需要两层循环。(第一层是「每个数都单独拿出来」,第二层是「单独拿出来的数和每个数逐一比较」)然后我们还需要一个数组 beat[n],来记每个数「击败」的次数。
那么我们可以写出这个逐一比较的双重循环:
for
然后我们还需要一个输出的循环。我们已经有了「击败」数 beat[i] 作为每个数的「排位」,那么我们每次使排位 - 1 就可以依次输出了。排位 - 1 需要一个 for 循环,找到对应的 i 又需要一个循环,因此这也是一个双重循环:
for
可能有重复数据导致排位相等的情况,但是相等者会一并输出,所以没有问题。
完整的参考代码如下:
左右滑动代码块以阅读完整内容。
#include
sort 函数
当然,我们上面的做法是比较复杂的(还不如 Excel)。实际上有一个函数可以给数组进行排序,它就是 sort 函数。
sort 函数包含在头文件
#include
中,它的使用方法如下:
sort
用这个函数做上面的案例分析 1 是很简单的,只需要十几行就可以写出来,这里不再赘述。
案例分析 2
现在我们来解决之前提到的 IPA 排序的问题:
问题描述:
输入一组学生的 IPA 值,要求将它们的 IPA 值(对应到学生的名字)降序输出。
关于输入:
输入分为两行:第一行是待排序的学生的总个数 n,之后的 n 行中,每一行依次为学生 A, B, C, ... 的 IPA 值,为两位小数。输入保证 n <= 26。
关于输出:
输出共 n 行,每一行都包括一个学生的名字(A, B, C, ... )以及他/她的 IPA 值(两位小数),两者以空格分隔;所有行按照 IPA 值降序排列。
输入示例:
6
3.99
4.00
3.95
3.92
3.98
3.90
输出示例:
B 4.00
A 3.99
E 3.98
C 3.95
D 3.92
F 3.90
这里因为要对应到「名字」输出(相当于 i 和 a[i] 的对应不能弄乱),所以只用 sort 是不行的。而我们在案例分析 1 中,没有改变 i 和 a[i] 的对应,因此可以借鉴之前的思路来解决这个问题。
我们注意到输入是按 A, B, C, D, ... 的顺序进行的,它们可以被一个 for 循环分别读入到一个数组 a[i] 的第 0, 1, 2, 3, ... 位中去,而这些字母的 ASCII 码也是递增的关系,所以 i 值和名字是可以对应的。具体来说就是:定义一个 char m = 'A',输出 a[i] 的同时输出 (char) (m + i) 就行了。
那么对上面的程序稍作修改,把 int 变成 float 或 double,再控制两位小数就可以了。参考代码如下:(没写注释了)
左右滑动代码块以阅读完整内容。
#include
在这种情况下,sort 函数反而会使程序更复杂了。(我们在讨论类似的问题的时候,已经有同学试过了)
练习:称体重
如果你有北大编程网格的账号,可以直接到下面的网站里做题:
http://www.pkupc.cn/programming/problem/95ea5733b34344e0a3cc68f0f19d7256/show.dowww.pkupc.cn描述:
赵、钱、孙、李四个人中既有大人也有小孩,给他们称体重时发现,他们每个人的体重都不一样,且体重(单位:公斤)恰好是 10 的整数倍,且他们的体重都不高于 50 公斤,已知赵、钱两人的体重之和恰好等于孙、李两人的体重之和;赵、李两人的体重之和大于孙、钱两人的体重之和,并且赵、孙俩人的体重之和还小于钱的体重。
请编写一个程序,按照由大到小的顺序,打印出四人的姓氏的首字母和体重数(中间用空格隔开,每人一行)。
关于输出:
打印出四人的姓氏的首字母和体重数(中间用空格隔开,每人一行)。
输出示例:(不一定是正确答案)
z 10
q 20
s 30
l 40
解析:
本题综合了计算机的解题逻辑和数据的排序。
参考代码如下:
左右滑动代码块以阅读完整内容。
#include
输出结果应该是
l 50
q 40
z 20
s 10