算法库统一以“迭代器区间 [first, last)”为参数——左闭右开,last 指向最后一个元素的下一个位置。理解了这一点,所有算法的参数形式就都好记了。
8.1 排序与 lambda
std::sort 是你在算法题里用得最多的函数。配合 lambda,任何排序规则都能写。
sort + lambda:多条件排序
lambda 的语法是 [捕获](参数) { 函数体 },就地写一个匿名函数。排序规则里先比分数降序、相同再比名字升序,一个 lambda 搞定。
std::sort(v.begin(), v.end(), [](const Student& a, const Student& b) {
if (a.score != b.score) { return a.score > b.score; }
return a.name < b.name;
});
lambda 的捕获
[capture] 控制 lambda 怎么访问外部变量:[x] 按值捕获、[&x] 按引用捕获、[&] 全部按引用、[=] 全部按值。std::count_if(v.begin(), v.end(), [lowest](int x){ return x >= lowest; }) 能就近读取上下文变量。
✍️ 本节练习
读入 n 个学生(名字、分数),用 std::sort + lambda 按分数从高到低排序,分数相同按名字字典序升序,输出排序结果。
输入 第一行 n;接下来 n 行“名字 分数”。
输出 排序后每行“名字 分数”。
3
Ann 90
Bob 95
Cid 90Bob 95
Ann 90
Cid 90💡 提示 lambda 里先比 score 降序,相等再比 name 升序。
✅ 查看参考答案与解析
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
struct Student { std::string name; int score; };
int main() {
int n;
std::cin >> n;
std::vector<Student> v(n);
for (int i = 0; i < n; ++i) { std::cin >> v[i].name >> v[i].score; }
std::sort(v.begin(), v.end(), [](const Student& a, const Student& b) {
if (a.score != b.score) { return a.score > b.score; }
return a.name < b.name;
});
for (const auto& s : v) { std::cout << s.name << ' ' << s.score << '\n'; }
return 0;
}
解析 比较器必须是严格弱序:相等返回 false。lambda 就近定义规则,最省事。
读入 n 个整数(可正可负),按绝对值从小到大排序;绝对值相同则负数在前。输出排序结果。
输入 第一行 n;第二行 n 个整数。
输出 排序后的序列。
5
-2 3 -1 2 1-1 1 -2 2 3💡 提示 std::abs 在 <cstdlib>;lambda 里比 abs(a) 和 abs(b),相等时负数优先(a < b)。
✅ 查看参考答案与解析
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <vector>
int main() {
int n;
std::cin >> n;
std::vector<int> v(n);
for (int i = 0; i < n; ++i) { std::cin >> v[i]; }
std::sort(v.begin(), v.end(), [](int a, int b) {
if (std::abs(a) != std::abs(b)) { return std::abs(a) < std::abs(b); }
return a < b; // 绝对值相同,负数在前
});
for (size_t i = 0; i < v.size(); ++i) {
std::cout << v[i] << (i + 1 == v.size() ? '\n' : ' ');
}
return 0;
}
解析 自定义比较器可以随意定义规则,只要满足严格弱序。
8.2 erase-remove 与二分查找
std::remove 这个名字有误导性:它不删除元素,只是把要保留的元素往前挪并返回新的逻辑结尾。真正删除要靠容器的 erase。
erase-remove 惯用法
v.erase(std::remove(v.begin(), v.end(), 2), v.end()); 删除所有等于 2 的元素。为什么拆两步?因为算法只拿到迭代器,不知道背后是 vector 还是数组,无法真正改变容器大小。按条件删用 remove_if + lambda。去重用 sort + unique + erase。
v.erase(std::remove(v.begin(), v.end(), 2), v.end());
v.erase(std::remove_if(v.begin(), v.end(),
[](int x) { return x % 2 == 0; }), v.end());
三个二分函数的明确分工
lower_bound 找第一个 >= x(左边界);upper_bound 找第一个 > x(右边界之后);两者相减 = x 出现的次数。binary_search 只回答“在不在”,需要位置信息时别用它。
auto lo = std::lower_bound(v.begin(), v.end(), 3); // 第一个 >= 3
auto up = std::upper_bound(v.begin(), v.end(), 3); // 第一个 > 3
std::cout << (up - lo); // 3 的个数
✍️ 本节练习
读入 n 个整数存入 vector,用 erase-remove 惯用法删除所有等于 2 的元素,输出剩余元素。
输入 第一行 n;第二行 n 个整数。
输出 删除 2 后剩下的数,空格分隔。
7
1 2 3 2 4 2 51 3 4 5💡 提示 v.erase(std::remove(v.begin(), v.end(), 2), v.end());
✅ 查看参考答案与解析
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
int n;
std::cin >> n;
std::vector<int> v(n);
for (int i = 0; i < n; ++i) { std::cin >> v[i]; }
v.erase(std::remove(v.begin(), v.end(), 2), v.end());
for (size_t i = 0; i < v.size(); ++i) {
std::cout << v[i] << (i + 1 == v.size() ? '\n' : ' ');
}
return 0;
}
解析 std::remove 只把保留的元素前移,返回新逻辑结尾;erase 负责真正改变 size。
读入 n 个整数排序,再读入一个 x,用 lower_bound 统计有多少个数 >= x。
输入 第一行 n;第二行 n 个整数;第三行 x。
输出 >= x 的个数。
6
1 3 3 5 7 9
35💡 提示 lower_bound 找第一个 >= x 的位置 it,个数 = v.end() - it。
✅ 查看参考答案与解析
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
int n;
std::cin >> n;
std::vector<int> v(n);
for (int i = 0; i < n; ++i) { std::cin >> v[i]; }
std::sort(v.begin(), v.end());
int x;
std::cin >> x;
auto it = std::lower_bound(v.begin(), v.end(), x);
std::cout << (v.end() - it) << '\n';
return 0;
}
解析 lower_bound 返回第一个 >= x 的位置;它到末尾的距离就是 >= x 的个数。
本章小结
- 算法以 [first, last) 区间为参数,左闭右开。
- 比较器必须严格弱序;lambda 就近定规则最省事。
- remove 只搬移不删除,必须配 erase;去重 = sort + unique + erase。
- lower_bound/upper_bound 相减得到出现次数;set/map 用成员版二分。
🧩 本章综合练习
这几道题把本章多个知识点串起来,建议合上资料独立完成,再展开答案对照。
读入 n 个学生(名字 分数),按分数降序、同分按名字升序输出;用 std::count_if 统计及格人数;把分数单独排序后用 std::lower_bound 统计 90 分及以上人数。
输入 第一行整数 n;接下来 n 行,每行「名字 分数」。
输出 先输出排序后的 n 行「名字 分数」;再输出 pass=、ge90=。
5
Ann 90
Bob 59
Cid 90
Dan 72
Eve 100Eve 100
Ann 90
Cid 90
Dan 72
Bob 59
pass=4
ge90=3💡 提示 比较器必须严格弱序:相等时返回 false;lower_bound 要求区间已升序。
✅ 查看参考答案与解析
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
struct Student {
std::string name;
int score{};
};
int main() {
int n{};
std::cin >> n;
std::vector<Student> v(n);
std::vector<int> scores;
scores.reserve(static_cast<std::size_t>(n));
for (int i = 0; i < n; ++i) {
std::cin >> v[i].name >> v[i].score;
scores.push_back(v[i].score);
}
std::sort(v.begin(), v.end(), [](const Student& a, const Student& b) {
if (a.score != b.score) { return a.score > b.score; } // 分数降序
return a.name < b.name; // 同分名字升序
});
for (const auto& s : v) { std::cout << s.name << ' ' << s.score << '\n'; }
int pass = static_cast<int>(std::count_if(
v.begin(), v.end(), [](const Student& s) { return s.score >= 60; }));
std::cout << "pass=" << pass << '\n';
std::sort(scores.begin(), scores.end()); // 升序才能二分
auto first90 = std::lower_bound(scores.begin(), scores.end(), 90);
std::cout << "ge90=" << std::distance(first90, scores.end()) << '\n';
return 0;
}
解析 count_if 用一个返回 bool 的 lambda 统计;lower_bound 返回第一个不小于 90 的位置,用 distance 算数量。
STL 算法 · lambda 表达式 · 二分查找