Lambda 表达式在算法竞赛中的应用
一、什么是 Lambda 表达式?
Lambda 表达式是 C++11 引入的匿名函数特性,允许在代码中直接定义简短的函数,而无需单独声明和定义。
基本语法
cpp
[capture](parameters) -> return_type {
// 函数体
}
| 部分 | 说明 | 示例 |
|---|---|---|
| capture | 捕获列表,用于访问外部变量 | []、[&]、[=]、[x] |
| parameters | 参数列表 | (int a, int b) |
| return_type | 返回类型(可省略) | -> int |
| 函数体 | 具体逻辑 | { return a + b; } |
简单示例
cpp
auto add = [](int a, int b) {
return a + b;
};
cout << add(3, 5) << "\n"; // 输出 8
二、Lambda 在 sort 中的应用
1. 基础排序
cpp
vector<int> a = {5, 2, 8, 1, 9, 3};
// 升序(默认)
sort(a.begin(), a.end());
// 降序
sort(a.begin(), a.end(), [](int x, int y) {
return x > y;
});
2. 结构体多关键字排序
cpp
struct Student {
string name;
int score;
int age;
};
vector<Student> stu = {
{"Alice", 90, 20},
{"Bob", 85, 21},
{"Charlie", 90, 19}
};
// 按分数降序,分数相同时按年龄升序
sort(stu.begin(), stu.end(), [](const Student& a, const Student& b) {
if (a.score != b.score) {
return a.score > b.score; // 分数高的在前
}
return a.age < b.age; // 年龄小的在前
});
3. pair 排序
cpp
vector<pair<int, int>> p = {{1, 5}, {2, 3}, {1, 2}, {3, 1}};
// 第一关键字升序,第二关键字降序
sort(p.begin(), p.end(), [](const pair<int, int>& a, const pair<int, int>& b) {
if (a.first != b.first) {
return a.first < b.first;
}
return a.second > b.second;
});
4. 区间调度问题(贪心)
cpp
// 按结束时间升序排序
vector<pair<int, int>> intervals = {{1, 3}, {2, 5}, {3, 6}, {5, 7}};
sort(intervals.begin(), intervals.end(), [](const pair<int, int>& a, const pair<int, int>& b) {
return a.second < b.second;
});
三、Lambda 在 priority_queue 中的应用
1. 基础用法
cpp
// 大根堆(默认)
priority_queue<int> maxHeap;
// 小根堆
priority_queue<int, vector<int>, greater<int>> minHeap;
2. 自定义比较函数
cpp
// pair 优先队列:按 first 升序(小根堆)
auto cmp = [](const pair<int, int>& a, const pair<int, int>& b) {
return a.first > b.first; // 注意:priority_queue 返回 true 表示优先级低
};
priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)> pq(cmp);
pq.push({1, 5});
pq.push({2, 3});
pq.push({1, 2});
cout << pq.top().first << " " << pq.top().second << "\n"; // 1 2
3. 结构体优先队列
cpp
struct Task {
string name;
int priority;
int deadline;
};
// 按优先级降序,优先级相同时按截止时间升序
auto cmpTask = [](const Task& a, const Task& b) {
if (a.priority != b.priority) {
return a.priority < b.priority; // 优先级低的沉底
}
return a.deadline > b.deadline; // 截止时间晚的沉底
};
priority_queue<Task, vector<Task>, decltype(cmpTask)> taskPQ(cmpTask);
4. Dijkstra 算法
cpp
// 小根堆:{距离, 节点}
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;
四、Lambda 在其他 STL 算法中的应用
1. find_if
cpp
vector<int> a = {1, 2, 3, 4, 5};
// 查找第一个大于 3 的元素
auto it = find_if(a.begin(), a.end(), [](int x) {
return x > 3;
});
if (it != a.end()) {
cout << "找到: " << *it << "\n"; // 找到: 4
}
2. count_if
cpp
vector<int> a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 统计偶数个数
int cnt = count_if(a.begin(), a.end(), [](int x) {
return x % 2 == 0;
});
cout << "偶数个数: " << cnt << "\n"; // 5
3. remove_if
cpp
vector<int> a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 删除所有偶数
a.erase(remove_if(a.begin(), a.end(), [](int x) {
return x % 2 == 0;
}), a.end());
// a 现在是 {1, 3, 5, 7, 9}
4. transform
cpp
vector<int> a = {1, 2, 3, 4, 5};
vector<int> b(a.size());
// 每个元素平方
transform(a.begin(), a.end(), b.begin(), [](int x) {
return x * x;
});
// b 现在是 {1, 4, 9, 16, 25}
5. for_each
cpp
vector<int> a = {1, 2, 3, 4, 5};
// 打印每个元素
for_each(a.begin(), a.end(), [](int x) {
cout << x << " ";
});
cout << "\n";
6. all_of / any_of / none_of
功能:判断范围内元素是否满足特定条件
| 算法 | 功能 | 返回值 |
|---|---|---|
all_of |
是否所有元素都满足条件 | 全是 true 返回 true |
any_of |
是否存在元素满足条件 | 有一个 true 就返回 true |
none_of |
是否没有元素满足条件 | 全是 false 返回 true |
时间复杂度 :O(n),但找到结果后会提前退出
示例:
cpp
vector<int> a = {2, 4, 6, 8, 10};
// 是否全是偶数
bool allEven = all_of(a.begin(), a.end(), [](int x) {
return x % 2 == 0;
});
cout << allEven << "\n"; // 1 (true)
// 是否有大于 5 的元素
bool hasBig = any_of(a.begin(), a.end(), [](int x) {
return x > 5;
});
cout << hasBig << "\n"; // 1 (true)
// 是否没有奇数
bool noOdd = none_of(a.begin(), a.end(), [](int x) {
return x % 2 != 0;
});
cout << noOdd << "\n"; // 1 (true)
提前退出机制:
cpp
vector<int> b = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// all_of 遇到第一个 false 就停止
// 这里检查到 1 时就知道不是全偶数,直接返回 false
all_of(b.begin(), b.end(), [](int x) {
return x % 2 == 0;
});
// any_of 遇到第一个 true 就停止
// 这里检查到 6 时就知道有大于 5 的,直接返回 true
any_of(b.begin(), b.end(), [](int x) {
return x > 5;
});
竞赛实用场景:
cpp
// 判断是否存在负数
bool hasNegative = any_of(a.begin(), a.end(), [](int x) {
return x < 0;
});
// 判断是否全为正数
bool allPositive = none_of(a.begin(), a.end(), [](int x) {
return x <= 0;
});
// 判断是否全为 0
bool allZero = all_of(a.begin(), a.end(), [](int x) {
return x == 0;
});
五、Lambda 捕获列表详解
1. 捕获方式
| 捕获方式 | 说明 | 示例 |
|---|---|---|
[] |
不捕获任何变量 | [] { return 1; } |
[&] |
引用捕获所有外部变量 | [&] { return x + y; } |
[=] |
值捕获所有外部变量 | [=] { return x + y; } |
[x] |
值捕获变量 x | [x] { return x * 2; } |
[&x] |
引用捕获变量 x | [&x] { x++; } |
[=, &x] |
值捕获所有,但 x 引用捕获 | [=, &x] { x++; } |
2. 竞赛实用示例
cpp
int n = 10;
vector<int> a(n);
// 值捕获 n
sort(a.begin(), a.end(), [=](int x, int y) {
return x < y;
});
// 引用捕获 a(可修改)
for_each(a.begin(), a.end(), [&](int &x) {
x *= 2;
});
六、竞赛实战案例
1. 自定义排序 + 贪心
cpp
// 活动选择问题
struct Activity {
int start, end, id;
};
int main() {
int n;
cin >> n;
vector<Activity> acts(n);
for (int i = 0; i < n; i++) {
cin >> acts[i].start >> acts[i].end;
acts[i].id = i + 1;
}
// 按结束时间升序排序
sort(acts.begin(), acts.end(), [](const Activity& a, const Activity& b) {
return a.end < b.end;
});
int cnt = 0, lastEnd = -1;
for (auto& act : acts) {
if (act.start >= lastEnd) {
cnt++;
lastEnd = act.end;
}
}
cout << cnt << "\n";
return 0;
}
2. 离散化 + 排序
cpp
vector<int> a = {5, 2, 8, 1, 9, 3};
vector<int> vals = a;
// 排序 + 去重
sort(vals.begin(), vals.end());
vals.erase(unique(vals.begin(), vals.end()), vals.end());
// 离散化
for (int &x : a) {
x = lower_bound(vals.begin(), vals.end(), x) - vals.begin() + 1;
}
3. 自定义哈希函数(unordered_map)
cpp
struct PairHash {
size_t operator()(const pair<int, int>& p) const {
return hash<int>()(p.first) ^ hash<int>()(p.second);
}
};
unordered_map<pair<int, int>, int, PairHash> mp;
mp[{1, 2}] = 10;
七、Lambda 核心要点总结
| 要点 | 说明 |
|---|---|
| sort 比较 | return true 表示 a 排在 b 前面 |
| priority_queue 比较 | return true 表示 a 优先级低于 b(沉底) |
| 捕获列表 | [&] 引用捕获,[=] 值捕获,竞赛中常用 [&] |
| auto 推导 | 返回类型可省略,编译器自动推导 |
| 性能 | Lambda 与普通函数性能相同,编译器会内联优化 |
记忆口诀
- sort :
true= 排前面 - priority_queue :
true= 沉底(优先级低) - 小根堆 :
a > b - 大根堆 :
a < b
八、完整模板代码(竞赛可直接复制)
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <functional>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// === sort 示例 ===
vector<int> a = {5, 2, 8, 1, 9, 3};
// 降序
sort(a.begin(), a.end(), [](int x, int y) {
return x > y;
});
// === priority_queue 示例 ===
// 小根堆
priority_queue<int, vector<int>, greater<int>> minHeap;
// 自定义比较
auto cmp = [](const pair<int, int>& a, const pair<int, int>& b) {
return a.first > b.first; // 小根堆
};
priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)> pq(cmp);
// === STL 算法示例 ===
vector<int> b = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 统计偶数
int cnt = count_if(b.begin(), b.end(), [](int x) {
return x % 2 == 0;
});
return 0;
}
九、常见错误与注意事项
1. priority_queue 比较函数写反
cpp
// 错误:这是大根堆
auto cmp = [](int a, int b) {
return a < b; // 错误!priority_queue 中 true 表示优先级低
};
// 正确:小根堆
auto cmp = [](int a, int b) {
return a > b; // 正确!
};
2. 捕获列表使用不当
cpp
int n = 10;
// 错误:值捕获无法修改 n
auto f1 = [=]() {
n++; // 编译错误
};
// 正确:引用捕获可以修改
auto f2 = [&]() {
n++; // 正确
};
3. Lambda 作为模板参数
cpp
// 错误:不能直接作为模板参数
priority_queue<int, vector<int>, [](int a, int b) { return a > b; }> pq;
// 正确:使用 decltype
auto cmp = [](int a, int b) { return a > b; };
priority_queue<int, vector<int>, decltype(cmp)> pq(cmp);
总结 :Lambda 表达式是竞赛中最常用的特性之一,尤其在自定义排序、优先队列、STL 算法中不可或缺。掌握 Lambda 可以大幅简化代码,提高编写效率。