算法笔记 P103 例题:【PAT A1025】PAT Ranking
题目
Programming Ability Test (PAT) is organized by the College of Computer Science and Technology of Zhejiang University. Each test is supposed to run simultaneously in several places, and the ranklists will be merged immediately after the test. Now it is your job to write a program to correctly merge all the ranklists and generate the final rank.
Input Specification:
Each input file contains one test case. For each case, the first line contains a positive number N (≤100), the number of test locations. Then N ranklists follow, each starts with a line containing a positive integer K (≤300), the number of testees, and then K lines containing the registration number (a 13-digit number) and the total score of each testee. All the numbers in a line are separated by a space.
Output Specification:
For each test case, first print in one line the total number of testees. Then print the final ranklist in the following format:
registration_number final_rank location_number local_rank
The locations are numbered from 1 to N. The output must be sorted in nondecreasing order of the final ranks. The testees with the same score must have the same rank, and the output must be sorted in nondecreasing order of their registration numbers.
Sample Input:
2
5
1234567890001 95
1234567890005 100
1234567890003 95
1234567890002 77
1234567890004 85
4
1234567890013 65
1234567890011 25
1234567890014 100
1234567890012 85
Sample Output:
9
1234567890005 1 1 1
1234567890014 1 2 1
1234567890001 3 1 2
1234567890003 3 1 2
1234567890004 5 1 4
1234567890012 5 2 2
1234567890002 7 1 5
1234567890013 8 2 3
1234567890011 9 2 4
思路
- 定义结构 Testee,用来存放每个学生的 registration_number,grade,final_rank,location_number,local_rank
- 声明数组 testees,所有数据最终保存在 testees 中
- 用 N 轮大循环接收数据,在每轮大循环中:
- 用 K 更新总人数
- 用 temp_testees 数组接收一个考场的考生信息,包括 registration_number,grade,顺便写入 location_number
- 对 temp_testees 进行排名,将名次写入 local_rank
- 根据最新的总人数重新分配 testees 的大小,并将 temp_testees 的数据附加到 testees 的后面
- 返回 3,直到所有数据都被写入 testees
- 对 testees 进行排名,将名次写入 final_rank
- 根据 testees 输出答案
代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct{
char registration_number[14];
int grade;
int final_rank;
int location_number;
int local_rank;
}Testee;
// 读入长度为 n 的 Testee 数组,根据成绩生成本地排名
void writeLocalRank(int n, Testee *testees);
// 读入长度为 n 的 Testee 数组,根据成绩生成最终排名
void writeFinalRank(int n, Testee *testees);
// qsort 的排序函数,根据成绩以及考生号码排名
int compareRank(const void *ct1, const void *ct2);
int main(){
int N, n;
int K, k;
int total_number = 0; // 统计总人数
// testees 数组用于保存所有人的数据,先分配空间以便后面重新分配
Testee *testees = (Testee*)malloc(sizeof(Testee));
scanf("%d", &N);
for (n = 1; n <= N; n++){
scanf("%d", &K);
total_number += K; // 统计总人数
Testee *temp_testees = (Testee*)malloc(K * sizeof(Testee));
for (k = 0; k < K; k++){
scanf("%s", temp_testees[k].registration_number);
scanf("%d", &temp_testees[k].grade);
temp_testees[k].location_number = n; // 顺便写入 location_number
}
// 根据成绩进行本地排名,并写入 location_rank
qsort(temp_testees, K, sizeof(Testee), compareRank);
writeLocalRank(K, temp_testees);
// 根据最新的总人数重新分配 testees 的大小,并将最新的一组本地数据接在后面
testees = (Testee*)realloc(testees, total_number * sizeof(Testee));
memcpy(testees + total_number - K, temp_testees, K * sizeof(Testee));
}
// 根据成绩进行最终排名,并写入 final_rank
qsort(testees, total_number, sizeof(Testee), compareRank);
writeFinalRank(total_number, testees);
// 根据 testees 数组输出答案
printf("%d\n", total_number);
int i;
for (i = 0; i < total_number; ++i){
printf("%s", testees[i].registration_number);
printf(" %d", testees[i].final_rank);
printf(" %d", testees[i].location_number);
printf(" %d\n", testees[i].local_rank);
}
return 0;
}
void writeLocalRank(int n, Testee *testees){
int i, equal;
equal = 0;
testees[0].local_rank = 1;
for (i = 1; i < n; ++i){
if (testees[i].grade < testees[equal].grade){
testees[i].local_rank = i + 1;
equal = i;
} else { // 成绩相等
testees[i].local_rank = testees[equal].local_rank;
}
}
}
void writeFinalRank(int n, Testee *testees){
int i, equal;
equal = 0;
testees[0].final_rank = 1;
for (i = 1; i < n; ++i){
if (testees[i].grade < testees[equal].grade){
testees[i].final_rank = i + 1;
equal = i;
} else { // 成绩相等
testees[i].final_rank = testees[equal].final_rank;
}
}
}
int compareRank(const void *ct1, const void *ct2){
Testee t1 = *(Testee*)ct1;
Testee t2 = *(Testee*)ct2;
if (t1.grade != t2.grade)
return t2.grade - t1.grade;
else
return strcmp(t1.registration_number, t2.registration_number);
}
关于 qsort() 的比较函数
AC之后看《算法笔记》上的代码,学到了写比较函数的小技巧,我原本的写法长这样,一串 else if 很繁琐:
int myCompareRank(const void *ct1, const void *ct2){
int ret = 0;
Testee t1 = *(Testee*)ct1;
Testee t2 = *(Testee*)ct2;
if (t1.grade < t2.grade)
ret = 1;
else if (t1.grade > t2.grade)
ret = -1;
else if (strcmp(t1.registration_number, t2.registration_number) > 0)
ret = 1;
else if (strcmp(t1.registration_number, t2.registration_number) < 0)
ret = -1;
return ret;
}
事实上 qsort() 只关心你返回的是正数,负数还是零,所以不用自己返回常数,直接返回排序关键字的运算结果即可。
但是要注意数据范围,直接返回相减结果可能导致溢出。
2020.03.27更新:
看了算法笔记 P120 的比较函数写法,觉得自己好傻。
本题代码中,比较函数的某一行是这样的:
return t2.grade - t1.grade;
简洁,但是数据大的时候会溢出,不安全
事实上有兼顾安全和简洁的写法:
return t2.grade > t1.grade;
2020.04.08更新:
return t2.grade > t1.grade;
这样的写法只有待排序元素没有重复值的时候可行。
总结一下:
- 数据没有重复值:用
>
或者<
; - 有重复值,但是数据不大,不会溢出:用
-
,返回相减结果; - 有重复值,且会溢出:老老实实写
if-else
。