洛谷 B4502 GESP202603 四级 礼盒排序------多关键字排序与排名计数法
📌 摘要
B4502 给定 n 个礼盒,每个含 k 件商品,按四级关键字排序:总价升序 → 最贵商品升序 → 最便宜商品升序 → 编号升序。这本质是多关键字字典序排序 (Lexicographic Multi-Key Sort)。原始代码没有用 sort + 自定义比较函数,而是用了排名计数法 :对每个礼盒,数有几个礼盒比它"更优",用这个计数作为最终位置。这种 O(n²) 的做法在 n≤10³ 时可行,且思路直白------不做交换,只数排名。本文从伪代码出发,延伸到数据库的 ORDER BY 多列排序、SQL 引擎的排序策略,以及排名计数法与计数排序(Counting Sort)的数学关系。
题目链接 :B4502 礼盒排序
📚 目录
-
[📝 前言](#📝 前言)
-
[🔍 题目在考什么](#🔍 题目在考什么)
-
[💡 思路](#💡 思路)
-
[📝 伪代码](#📝 伪代码)
-
[🎯 关键点](#🎯 关键点)
-
[⚠️ 注意事项](#⚠️ 注意事项)
-
[🌳 延伸:从礼盒到数据库------多关键字排序的工业实践](#🌳 延伸:从礼盒到数据库——多关键字排序的工业实践)
-
[🔤 字典序:不止用于字符串](#🔤 字典序:不止用于字符串)
-
[🗄️ ORDER BY:数据库的多关键字排序](#🗄️ ORDER BY:数据库的多关键字排序)
-
[🏆 排名计数法:用计数代替交换](#🏆 排名计数法:用计数代替交换)
-
[📊 计数排序:从排名到线性时间](#📊 计数排序:从排名到线性时间)
-
[🛒 电商排序:你每天在用的多关键字排序](#🛒 电商排序:你每天在用的多关键字排序)
-
-
[📚 延伸阅读文献](#📚 延伸阅读文献)
📝 前言
这篇题解没有源代码,只有伪代码。
作为一名信奥教练,我不提倡复制粘贴。我见过太多学生搜到题解、复制、粘贴、提交、AC------代码跑通了,脑子没跑通。下次遇到变体题,还是不会。
伪代码剥掉了语言的壳,只留算法的骨架。你看不到 #include,看不到 cin、cout,看不到那些让你以为"我会了"的语法细节。你能看到的只有:这一步做什么、下一步做什么、为什么这么做。
如果你是路过的友友,已经在这道题上挣扎了很久------先去喝杯水,回来重新看看自己卡在哪一步。是没读懂题意?是思路方向偏了?还是代码有 bug 但逻辑其实对?大多数时候不是不会,是走偏了。偏了不可怕,可怕的是偏了之后直接放弃,去抄一份能 AC 的代码。抄完你以为你懂了,其实你只是搬了别人的结论。
除非你时间真的紧张------比赛临近、作业要交------那种情况先 AC 再说,能理解。但平时练习,给自己一点耐心。先自己想、自己写、自己调,跑不过了再来看伪代码:你的思路和这里差在哪一步。那一步,就是你真正学到的东西。
🔍 题目在考什么
n 个礼盒,每个含 k 件商品。按四个关键字排序:
| 优先级 | 关键字 | 方向 |
|---|---|---|
| 1 | 礼盒总价(k 件商品价格之和) | 升序 |
| 2 | 最贵商品价格 | 升序 |
| 3 | 最便宜商品价格 | 升序 |
| 4 | 礼盒编号 | 升序 |
这是多关键字字典序排序:先比第一关键字,平了比第二,再平比第三,最后比第四。
数据范围 n≤10³,k≤10,O(n²) 的排序算法完全可行。
💡 思路
原始代码用了排名计数法------不做交换,只数排名:
- 预处理 :对每个礼盒 i,计算总价、最大值、最小值,存入
temp_total[i] = [编号, 总价, 最大值, 最小值] - 排名计算:对每个礼盒 i,初始排名 = n(最末)。遍历所有其他礼盒 j,如果 j 比 i "更优"(按四级关键字比较),排名 -1
- 放置 :
sort_num[排名] = 礼盒编号
这和交换排序(冒泡、选择)不同------它不移动元素,只统计"有几个比我更优",用这个计数直接定位。
📝 伪代码
text
读取 n, k
读取礼盒数组 gifts[0..n-1][0..k-1]
// 第一步:预处理------算总价、最大值、最小值
对 i = 0 到 n-1:
编号[i] = i + 1
总价[i] = gifts[i][0..k-1] 之和
最大值[i] = gifts[i][0..k-1] 中的最大
最小值[i] = gifts[i][0..k-1] 中的最小
// 第二步:排名计数法
对 i = 0 到 n-1:
排名 = n // 初始假设排最后
对 j = 0 到 n-1: // 和每个其他礼盒比较
如果 j 比 i 更优: // 按四级关键字比较
排名--
sort_num[排名] = 编号[i] // 放到对应位置
// 第三步:输出
对 index = 0 到 n-1:
输出 sort_num[index] + 空格
"j 比 i 更优"的比较逻辑(四级关键字字典序):
text
函数 更优(j, i):
如果 总价[j] < 总价[i]: 返回 true // 第一关键字:总价更小
如果 总价[j] == 总价[i]:
如果 最大值[j] < 最大值[i]: 返回 true // 第二关键字:最大值更小
如果 最大值[j] == 最大值[i]:
如果 最小值[j] < 最小值[i]: 返回 true // 第三关键字:最小值更小
如果 最小值[j] == 最小值[i]:
如果 编号[j] < 编号[i]: 返回 true // 第四关键字:编号更小
返回 false
🎯 关键点
多关键字比较 = 字典序。 两个礼盒比较时,先比总价,总价相同比最大值,最大值相同比最小值,最小值相同比编号。这和字符串字典序比较一模一样------先比第一个字符,相同比第二个......只是"字符"变成了"关键字"。
排名计数法的正确性。 对礼盒 i,如果有 m 个礼盒比它更优,那它的排名是第 m+1(0 基下标为 m)。因为所有关键字都能确定严格全序(最后有编号兜底),不会有两个礼盒排名相同。原始代码用"初始排名 = n,每遇到一个更优的 -1"实现这个逻辑。
"编号兜底"保证全序。 如果两个礼盒总价、最大值、最小值全部相同,最后比编号。编号互不相同,所以任意两个礼盒都能分出先后------不存在"并列"。
用样例追踪(n=4, k=3):
| 礼盒 | 商品价格 | 总价 | 最大值 | 最小值 |
|---|---|---|---|---|
| 1 | 3 5 2 | 10 | 5 | 2 |
| 2 | 4 1 5 | 10 | 5 | 1 |
| 3 | 2 2 4 | 8 | 4 | 2 |
| 4 | 3 4 3 | 10 | 4 | 3 |
排名计算:
| 礼盒 | 比它更优的有 | 排名(0基) | sort_num排名 |
|---|---|---|---|
| 1 | 2,3,4(3号总价更小;4号总价同但最大值更小;2号总价同、最大值同但最小值更小) | 3 | 1 |
| 2 | 3,4(3号总价更小;4号总价同但最大值更小;1号?总价同、最大值同、最小值2>1,所以1不比2优) | 2 | 2 |
| 3 | 无(总价最小) | 0 | 3 |
| 4 | 3(3号总价更小) | 1 | 4 |
输出:3 4 2 1。✓
为什么不用 sort? 原始代码选择手写排名计数而不是 sort + 自定义比较函数,可能是因为多关键字比较逻辑复杂,写成 cmp 函数容易出错。排名计数法虽然 O(n²),但逻辑直白:数有几个比你强,你就是第几名。对学生来说,这种"排名"思维比"交换"思维更直觉。
⚠️ 注意事项
-
VLA 问题。
int gifts[n][k]和int temp_total[n][4]是变长数组,不是标准 C++。可用vector或固定大小数组替代。 -
排名计数法的复杂度。 O(n²),n=10³ 时是 10⁶ 次比较,1 秒内没问题。但 n=10⁵ 时会超时------这时应该用
sort+ 比较函数 O(n log n)。 -
比较逻辑的完整性。 四级关键字必须完整实现,漏掉任何一级都会导致排序错误。特别是第四级"编号"------如果三级都相同但没比编号,两个礼盒会获得相同排名,
sort_num会覆盖。 -
"自比"处理。 原始代码中
i==j时需要特殊处理(不能自己比自己更优)。代码用if(i==j) temp_sort_index--处理------初始排名是 n(最末),自比时 -1 变成 n-1,然后再被其他更优的 -1。需要仔细理解这个逻辑。 -
输出格式。 编号之间用空格分隔,末尾也有空格。漏空格或多余换行会格式错误。
🌳 延伸:从礼盒到数据库------多关键字排序的工业实践
你在 B4502 里做的多关键字排序,在工业界有一个更常见的名字------ORDER BY。
🔤 字典序:不止用于字符串
字典序(Lexicographic Order)不局限于字符串比较------任何"有序的多个属性"都可以用字典序比较(Lexicographic Order --- Wikipedia):
| 场景 | 第一关键字 | 第二关键字 | 第三关键字 |
|---|---|---|---|
| B4502 礼盒 | 总价 | 最大值 | 最小值 |
| 字典排序 | 首字母 | 第二字母 | 第三字母 |
| 学生排名 | 总分 | 数学 | 语文 |
| 航班排序 | 价格 | 起飞时间 | 航程时长 |
| 商品搜索 | 相关度 | 销量 | 好评率 |
核心规则一样:先比第一关键字,平了比第二,再平比第三...... 你在 B4502 里写的 if 总价相同 → if 最大值相同 → if 最小值相同 → 比编号,和字典里查单词 apple 在 application 前面(第三个字母 p < l)做的是同一件事。
🗄️ ORDER BY:数据库的多关键字排序
SQL 的 ORDER BY 就是多关键字排序的直接实现(SQL ORDER BY --- W3Schools):
你的 B4502 等价于这条 SQL:
sql
SELECT box_id
FROM gifts
ORDER BY total_price ASC, max_price ASC, min_price ASC, box_id ASC;
| B4502 代码 | SQL 等价 |
|---|---|
temp_total[i][1](总价) |
total_price |
temp_total[i][2](最大值) |
max_price |
temp_total[i][3](最小值) |
min_price |
temp_total[i][0](编号) |
box_id |
| 四级 if-else 嵌套 | ORDER BY ... ASC, ... ASC, ... ASC, ... ASC |
数据库引擎在执行 ORDER BY 时,内部用什么算法?取决于数据量(Database Sorting --- VLDB):
| 数据量 | 数据库用的排序 | 对应你学过的 |
|---|---|---|
| 小(内存放得下) | 快排 / 内部归并排序 | sort() |
| 大(内存放不下) | 外部归并排序 | 归并排序的磁盘版 |
| 有索引 | 索引扫描(已排序) | 不需要排序 |
你的排名计数法 O(n²) 在工业界不会用------太慢。但它揭示了一个重要的概念:排序的本质是确定每个元素的"位置",而位置 = 比它更优的元素个数。
🏆 排名计数法:用计数代替交换
你的代码做了一件有趣的事------不交换任何元素,只数"有几个比我更优",用这个计数直接定位(Comparison Sort --- Wikipedia):
| 排序方法 | 核心操作 | 复杂度 | 思维模式 |
|---|---|---|---|
| 冒泡排序 | 相邻比较 + 交换 | O(n²) | "你比我大,换位置" |
| 选择排序 | 找最小 + 交换 | O(n²) | "找出最小的,放最前" |
| 排名计数法 | 数"有几个更优" | O(n²) | "数出排名,直接放" |
| 快速排序 | 分治 + 递归 | O(n log n) | "比基准小的去左边" |
排名计数法的优势是不需要交换 ------适合"元素很重(复制成本高)"的场景。比如排序大型结构体时,排名计数法只需要记一个 int 排名,最后一次性放到正确位置。缺点是 O(n²),大数据量不能用。
📊 计数排序:从排名到线性时间
排名计数法和计数排序 (Counting Sort)名字像但原理不同(Counting Sort --- Wikipedia):
| 你的排名计数法 | 计数排序 | |
|---|---|---|
| 数什么 | 数"有几个元素比我更优" | 数"值等于 v 的有几个" |
| 适用范围 | 任意可比较的元素 | 整数,值域有限 |
| 复杂度 | O(n²) | O(n + k) |
| 稳定性 | 稳定(编号兜底) | 稳定 |
但两者共享一个核心思想:不比较+交换,而是用计数确定位置。这是非比较排序的精髓------如果你能通过计数知道每个元素的最终位置,就不需要交换。
🛒 电商排序:你每天在用的多关键字排序
淘宝、京东的商品搜索结果排序,就是 B4502 的工业版(E-commerce Sorting --- Wikipedia):
| B4502 | 淘宝搜索 |
|---|---|
| 总价升序 | 价格升序(或综合评分降序) |
| 最大值升序 | 销量降序 |
| 最小值升序 | 好评率降序 |
| 编号升序 | 商品ID升序(兜底) |
当你在淘宝搜索"手机壳",选择"价格从低到高"时,后台执行的 SQL 就是:
sql
SELECT product_id
FROM products
WHERE category = '手机壳'
ORDER BY price ASC, sales DESC, rating DESC, product_id ASC;
这条 SQL 和你 B4502 的代码做的是完全一样的事------多关键字字典序排序。区别只是关键字不同、数据量大了几百万倍、底层排序算法从 O(n²) 变成了 O(n log n)。
📚 延伸阅读文献
论文与技术文档
- D. E. Knuth. The Art of Computer Programming, Vol. 3: Sorting and Searching (2nd Edition). Addison-Wesley, 1998. ------ 排序算法的圣经,含多关键字排序和计数排序。
- H. A. H. van der Velden. Sorting and Ranking in Information Retrieval. Information Processing & Management, 1975. ------ 信息检索中的排序与排名。
在线资源
- 洛谷. B4502 GESP202603 四级 礼盒排序 . https://www.luogu.com.cn/problem/B4502
- SQL ORDER BY Tutorial --- W3Schools . https://www.w3schools.com/sql/sql_orderby.asp ------ SQL 多列排序教程。
- Lexicographic Order --- Wikipedia . (Lexicographic Order) ------ 字典序的数学定义和扩展。
- Comparison Sort --- Wikipedia . (Comparison Sort) ------ 比较排序的下界证明(O(n log n))。
- Counting Sort --- Wikipedia . (Counting Sort) ------ 计数排序原理,和排名计数法的对比。
- Sort-Merge Join --- Wikipedia . (Sort-Merge Join) ------ 数据库外部排序策略。
- Recommender System --- Wikipedia . (Recommender System) ------ 推荐系统的多关键字排序。
推荐教材
-
T. H. Cormen, C. E. Leiserson, R. L. Rivest, C. Stein. Introduction to Algorithms (4th Edition). MIT Press, 2022. ------ 第 5 章排序,第 8 章线性时间排序(计数排序)。
-
H. Garcia-Molina, J. Ullman, J. Widom. Database Systems: The Complete Book (2nd Edition). Pearson, 2008. ------ 含 ORDER BY 的实现和外部排序。
-
R. Baeza-Yates, B. Ribeiro-Neto. Modern Information Retrieval (2nd Edition). Addison-Wesley, 2011. ------ 搜索引擎中的多关键字排序。
本文标签:#算法 #多关键字排序 #字典序 #排名计数法 #计数排序 #ORDER BY #数据库 #电商 #洛谷题解 #信奥 #GESP #C++ #四级
本文首发于 CSDN,作者:HugoStudio_SWAN