C++ 竞赛入门
sort 排序
默认升序、可传比较规则;stable_sort 保持同值元素的原顺序。
sort 排序
读完能做什么
- 能用
sort(v.begin(), v.end())把一组数据从小到大排好,并说出排完之后每个位置是什么。 - 能用
greater<int>()改成从大到小,也能只排区间里的一段位置。 - 能写一个自己的比较规则,实现“先按成绩、再按姓名”这类多关键字排序。
- 能说清
stable_sort保证“相等的元素保持原来的先后顺序”,而sort不做这个保证。
为什么需要它
一张成绩表常常需要按分数排好再处理:
| 姓名 | A | B | C |
|---|---|---|---|
| 成绩 | 90 | 80 | 90 |
手工排序要写两层循环、反复交换相邻元素,代码长还容易写错。更麻烦的是,题目里的排序要求经常在变:有时要升序,有时要降序,有时要先按成绩、成绩相同再按姓名。每次都手写一遍排序并不划算。
这类“把一段数据按某个规则重新排列”的动作在程序里出现得太多,标准库把它做成了一个函数,就是 sort。它处理的对象是“从哪个位置到哪个位置”的一段数据,规则由你给,也可以不给。
它是什么
一句话:sort(first, last) 把区间 [first, last) 里的元素按规则重新排列,默认从小到大。
几个必须就地看懂的概念:
区间 [first, last)。 迭代器是“指向容器中某个位置的游标”。v.begin() 指向第一个元素,v.end() 指向最后一个元素后面一个位置(尾后位置)。两个位置组成一个左闭右开区间:包含 first 指向的元素,不包含 last 指向的元素。所以 [v.begin(), v.end()) 正好是整个 vector。区间这套约定在 容器、迭代器与区间 里有系统讲解。
默认规则是 <。 不给第三个参数时,sort 用 < 判断“谁应该排在前面”:如果 a < b,就把 a 排在 b 前面,于是结果从小到大。
比较器:自己定“谁排在前面”。 第三个参数 comp 是一个判断规则:comp(a, b) 返回 true 表示 a 应该排在 b 前面。它可以是普通函数,也可以是函数对象(带 operator() 的结构体)。greater<int>() 就是标准库给的一个现成规则,含义正好和 < 相反,所以它能排出降序。比较规则的写法见 比较规则与函数对象。
随机访问迭代器。 sort 要求迭代器能随意跳转、能相减。vector、deque、string、普通数组都满足;list 的迭代器只能 ++ / --,不满足,所以 list 要用成员函数 l.sort()。
它会修改数据。 sort 没有返回值,它直接改动区间里元素的顺序,不需要你把结果接住。
适用场景:数据可以重新排列、你要的是有序结果。使用时要 #include <algorithm>,本页示例用 #include <bits/stdc++.h> 一次包含全部标准头文件。
最小可运行示例
用数据 [4, 1, 3, 1] 演示默认排序:
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> v = {4, 1, 3, 1};
sort(v.begin(), v.end()); // 默认按从小到大排序
for (int x : v) cout << x << ' ';
cout << '\n';
return 0;
}
输出:
1 1 3 4
逐行看:
vector<int> v = {4, 1, 3, 1};四个元素,下标依次是 0、1、2、3。sort(v.begin(), v.end())处理整个vector。v.begin()指向第一个 4,v.end()是最后一个元素后面那个位置,区间正好覆盖 4 个元素。- 排序后从小到大是
1 1 3 4,两个 1 并排。sort直接改动了v,循环按顺序打印就得到这一行。 - 输出末尾有一个空格,是因为循环每次都打印“数字 + 空格”。这不影响结果,只是打印格式。
一步步看变化
还是这组数据 [4, 1, 3, 1],依次做三次排序:先只排前两个位置,再排全体升序,最后排全体降序。
| 步骤 | 操作 | 参与排序的位置 | 操作后 |
|---|---|---|---|
| 0 | 初始 | — | 4 1 3 1 |
| 1 | sort(begin, begin+2) |
下标 0、1 | 1 4 3 1 |
| 2 | sort(begin, end) |
全部 4 个 | 1 1 3 4 |
| 3 | sort(begin, end, greater<int>()) |
全部 4 个 | 4 3 1 1 |
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> v = {4, 1, 3, 1};
sort(v.begin(), v.begin() + 2); // 只排前两个位置
cout << "只排前两个位置后:";
for (int x : v) cout << x << ' ';
cout << '\n';
sort(v.begin(), v.end()); // 全体升序
cout << "全体升序后:";
for (int x : v) cout << x << ' ';
cout << '\n';
sort(v.begin(), v.end(), greater<int>()); // 全体降序
cout << "全体降序后:";
for (int x : v) cout << x << ' ';
cout << '\n';
return 0;
}
输出:
只排前两个位置后:1 4 3 1
全体升序后:1 1 3 4
全体降序后:4 3 1 1
- 第 1 步:区间是
[v.begin(), v.begin() + 2),只覆盖下标 0、1 上的4、1,排成1、4;下标 2、3 的3、1没被碰,保持原样,得到1 4 3 1。 - 第 2 步:
[v.begin(), v.end())覆盖全部,排成1 1 3 4。 - 第 3 步:
greater<int>()让大的排在前面,排成4 3 1 1。
区间是左闭右开的,终点取不到。所以“只排前两个位置”要写 v.begin() + 2 而不是 v.begin() + 1。
逐项扩展核心操作
降序:greater<int>()
把第三个参数换成 greater<int>(),规则就变成“大的在前”:
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> v = {4, 1, 3, 1};
sort(v.begin(), v.end(), greater<int>());
for (int x : v) cout << x << ' ';
cout << '\n';
return 0;
}
输出:
4 3 1 1
greater<int>() 是一个函数对象:它带一个 operator(),greater<int>()(a, b) 判断 a > b。把它交给 sort,sort 就用 > 来决定先后,于是得到降序。
按自己的规则排序(多关键字)
数据换成带姓名和成绩的学生。要求“成绩高的在前,成绩相同则姓名小的在前”:
#include <bits/stdc++.h>
using namespace std;
struct Student {
string name;
int score;
};
// 规则:成绩高的排前面;成绩相同则姓名小的排前面
bool byScoreDescThenName(const Student& a, const Student& b) {
if (a.score != b.score) return a.score > b.score;
return a.name < b.name;
}
int main() {
vector<Student> s = {{"A", 90}, {"B", 80}, {"C", 90}};
sort(s.begin(), s.end(), byScoreDescThenName);
for (const Student& x : s) cout << x.name << ' ' << x.score << '\n';
return 0;
}
输出:
A 90
C 90
B 80
规则分两段读:
if (a.score != b.score) return a.score > b.score;:成绩不同时,用>让成绩高的排前面。return a.name < b.name;:只有成绩相同时才会走到这一句,用<让姓名小的排前面。
A 和 C 都是 90 分,进入第二段比较,"A" < "C" 为真,所以 A 排在 C 前面;B 只有 80 分,排在最后。
比较器必须满足严格弱序。 “严格弱序”是一组关于比较规则的约定,实际写作时记住两条:comp(x, x) 必须返回假(自己不能排在自己前面),以及“相等”的元素互相比较都必须返回假。所以判断用 <(或 >),不能用 <=:comp(x, x) 会返回真,排序行为就没有保证。多关键字时也要让每条分支单独满足这两条。
stable_sort:相等元素保持原顺序
看这组数据,A 和 C 的成绩相同:
#include <bits/stdc++.h>
using namespace std;
struct Student {
string name;
int score;
};
// 只比较成绩,不比较姓名
bool byScoreAsc(const Student& a, const Student& b) {
return a.score < b.score;
}
int main() {
vector<Student> s = {{"A", 90}, {"B", 80}, {"C", 90}};
// 稳定排序:成绩相同的学生保持原来的先后顺序
stable_sort(s.begin(), s.end(), byScoreAsc);
for (const Student& x : s) cout << x.name << ' ' << x.score << '\n';
return 0;
}
输出:
B 80
A 90
C 90
比较规则只看成绩,A 和 C 在它眼里“相等”。stable_sort 保证相等元素保持排序前的相对顺序:原来 A 在 C 前面,排完后 A 仍在 C 前面。加上 B,结果就是 B、A、C。
sort 不做这个保证。 标准没有规定 sort 会把相等的元素排成什么顺序,换一个编译器、换一组数据都可能不同。所以:
- 需要“同分保持原顺序”时,用
stable_sort,不要指望sort。 - 不要写“用
sort排完后同分的次序是……”这类结论,因为标准根本没规定它。 - 如果只关心成绩、不在意同分者的先后,用
sort就够了,而且通常更快。
关于 sort 和 unique 的配合:先用 sort 把相等的值聚到一起,再用 unique 合并相邻相等元素、erase 真正删除,就能实现“全体去重”。这个组合的说法见 unique 去重。
什么时候选它、哪里容易错
区间是左闭右开的。 sort(v.begin(), v.end()) 排全体;sort(v.begin() + 1, v.end()) 只排下标 1 到末尾。终点写多一格、少一格,参与排序的元素就变了。
默认升序,降序要显式给规则。 sort(v.begin(), v.end()) 是从小到大;要降序得写 sort(v.begin(), v.end(), greater<int>())。
比较器不能用 <=。 用 <= 时 comp(x, x) 会返回真,违反严格弱序,排序结果不可靠,某些编译器甚至会越界访问:
// 错误示例:比较器写成 a <= b 违反了严格弱序,结果不可靠
#include <bits/stdc++.h>
using namespace std;
bool lessOrEqual(int a, int b) {
return a <= b; // 相等时返回真,违反严格弱序
}
int main() {
vector<int> v = {4, 1, 3, 1};
sort(v.begin(), v.end(), lessOrEqual); // 错误:相等元素互相比较时返回真
return 0;
}
这段能编译,但 a <= b 让相等元素互相比较时返回真,破坏了排序算法的前提,运行结果没有保证。本页不给它写运行结果。正确写法是 a < b。
sort 需要随机访问迭代器。 list 的迭代器只能逐个前进,sort(l.begin(), l.end()) 编译不过,要用成员函数 l.sort()(见 list 链表)。
排序会打乱原来的位置。 排完之后元素的下标全变了,之前记下的迭代器或下标不再对应原来的元素。要按“原来的第几个”访问数据,得先另存一份。
复杂度。 sort 是 O(n log n)。stable_sort 也保证 O(n log n) 的比较次数,但为了保持稳定,通常需要额外空间,实现代价比 sort 高,所以没有特殊需要时优先用 sort。
选读。 sort 内部混合了快速排序、插入排序和堆排序(叫“内省式排序”),在递归太深时切换到堆排序避免退化。这些是实现细节,不影响你怎么调用它,也不保证任何“相等元素的顺序”。
操作速查表
| 写法 | 作用 | 备注 |
|---|---|---|
sort(first, last) |
把区间按 < 升序排序 |
默认从小到大 |
sort(first, last, comp) |
按自定义规则排序 | comp(a, b) 为真表示 a 在前 |
sort(v.begin(), v.end()) |
排整个 vector |
需要随机访问迭代器 |
sort(v.begin() + 1, v.end()) |
只排下标 1 到末尾 | 区间左闭右开 |
sort(a, a + n) |
排普通数组 | 指针就是迭代器 |
greater<int>() |
降序规则 | 大数排在前面 |
stable_sort(first, last, comp) |
稳定排序 | 相等元素保持原顺序 |
sort(v.begin(), v.end(), [](int a, int b){ return a < b; }) |
就地写规则 | 选读:lambda 表达式 |
l.sort() |
给 list 排序 |
成员函数,std::sort 对 list 不可用 |
动手检查
第 1 题:预测输出
下面是一段片段(省略了头文件和 main),请先手推出输出,再运行核对:
vector<int> v = {3, 1, 2, 1};
sort(v.begin(), v.end(), greater<int>());
for (int x : v) cout << x << ' ';
第 2 题:修改代码
下面这段片段把 [4, 1, 3, 1] 从小到大排序,请把它改成从大到小,让输出变成 4 3 1 1:
vector<int> v = {4, 1, 3, 1};
sort(v.begin(), v.end()); // 改成降序
for (int x : v) cout << x << ' ';
验证条件:先说出改完之后第一个元素是几,再运行核对。
参考结果与解释
第 1 题:输出是 3 2 1 1 。greater<int>() 让大的排在前面,3 1 2 1 从大到小排成 3 2 1 1。完整程序与实测输出:
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> v = {3, 1, 2, 1};
sort(v.begin(), v.end(), greater<int>());
for (int x : v) cout << x << ' ';
cout << '\n';
return 0;
}
输出:
3 2 1 1
第 2 题:给第三个参数补上 greater<int>(),写成 sort(v.begin(), v.end(), greater<int>());。它把默认的 < 规则换成 >,于是最大的 4 排在第一位。
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> v = {4, 1, 3, 1};
sort(v.begin(), v.end(), greater<int>());
for (int x : v) cout << x << ' ';
cout << '\n';
return 0;
}
输出:
4 3 1 1
立即练习
| 难度 | 题目 | 题解 | 训练目标 |
|---|---|---|---|
| 普及- | 洛谷 P1177:【模板】排序 | 题解 | 读入一组数,用 sort 从小到大输出,体会“长度由输入决定”时把数据放进 vector 再排序的写法。 |
| 普及- | 洛谷 P1104:生日 | 题解 | 按出生日期从早到晚输出;同年同月同日生的同学要求输入靠后的先输出,所以光比生日不够,要把输入序号也写进规则里(或反过来比较)。练习“把题目里的多重要求逐条翻译成比较规则”。 |
两道题都只需要数组、循环和本页讲过的排序操作。做完后请说明:第二题里“同生日的人输入靠后先输出”这条要求,你把它写在了规则的哪一句?如果题目改成“同生日保持输入顺序”,用 stable_sort 只比生日能不能做到?为什么?