1. STL算法库概述
STL算法库是C++标准模板库中最强大的组成部分之一,它提供了大量经过高度优化的通用算法,涵盖了排序、搜索、数值运算等常见操作。这些算法通过迭代器与容器解耦,使得相同的算法可以应用于不同类型的容器。
在实际开发中,我发现很多开发者虽然知道STL算法的存在,但往往只使用最简单的find和sort,而忽略了其他80%同样强大但未被充分利用的算法。掌握这些算法可以显著减少重复代码,提升程序性能和可读性。
STL算法主要分为以下几类:
- 非修改序列操作(如find、count)
- 修改序列操作(如copy、transform)
- 排序及相关操作(如sort、binary_search)
- 数值算法(如accumulate)
2. 常用非修改序列算法
2.1 查找算法
find和find_if是最常用的查找算法,但STL提供了更多针对性更强的变种:
cpp复制// 查找第一个匹配元素
auto it = std::find(v.begin(), v.end(), 42);
// 使用谓词查找
auto it = std::find_if(v.begin(), v.end(), [](int x) {
return x > 50 && x % 2 == 0;
});
// 查找第一个不满足条件的元素
auto it = std::find_if_not(v.begin(), v.end(), is_positive);
// 查找子序列
auto it = std::search(v1.begin(), v1.end(), v2.begin(), v2.end());
提示:对于已排序的区间,应使用binary_search等算法,它们具有O(log n)的时间复杂度。
2.2 计数和比较算法
count和count_if可以快速统计满足条件的元素个数:
cpp复制int num = std::count(v.begin(), v.end(), 5);
int even_num = std::count_if(v.begin(), v.end(), [](int x) {
return x % 2 == 0;
});
mismatch算法可以找出两个序列中第一个不相同的位置:
cpp复制auto pair = std::mismatch(v1.begin(), v1.end(), v2.begin());
if (pair.first != v1.end()) {
cout << "First mismatch: " << *pair.first << " vs " << *pair.second;
}
3. 常用修改序列算法
3.1 拷贝和填充算法
copy算法虽然简单,但在实际项目中有许多高效用法:
cpp复制// 基本拷贝
std::copy(src.begin(), src.end(), dest.begin());
// 拷贝满足条件的元素
std::copy_if(src.begin(), src.end(), dest.begin(), [](int x) {
return x > 0;
});
// 反向拷贝
std::copy_backward(src.begin(), src.end(), dest.end());
fill和generate可以快速初始化容器:
cpp复制// 填充固定值
std::fill(v.begin(), v.end(), 0);
// 使用生成器填充
int i = 0;
std::generate(v.begin(), v.end(), [&i]() { return i++; });
3.2 删除和替换算法
remove算法实际上并不删除元素,而是将不需要的元素移动到容器末尾:
cpp复制// "删除"所有值为5的元素
auto new_end = std::remove(v.begin(), v.end(), 5);
v.erase(new_end, v.end()); // 真正删除元素
replace可以批量修改元素值:
cpp复制// 替换所有5为10
std::replace(v.begin(), v.end(), 5, 10);
// 条件替换
std::replace_if(v.begin(), v.end(), [](int x) {
return x < 0;
}, 0);
4. 排序及相关算法
4.1 基本排序算法
sort是最常用的排序算法,但需要注意它要求随机访问迭代器:
cpp复制// 默认升序排序
std::sort(v.begin(), v.end());
// 自定义比较函数
std::sort(v.begin(), v.end(), [](int a, int b) {
return a > b; // 降序排序
});
对于部分排序需求,可以使用partial_sort:
cpp复制// 只排序前10个元素
std::partial_sort(v.begin(), v.begin()+10, v.end());
4.2 二分查找算法
已排序的区间可以使用更高效的查找算法:
cpp复制// 检查元素是否存在
bool found = std::binary_search(v.begin(), v.end(), 42);
// 查找第一个不小于42的元素
auto it = std::lower_bound(v.begin(), v.end(), 42);
// 查找第一个大于42的元素
auto it = std::upper_bound(v.begin(), v.end(), 42);
4.3 其他排序相关算法
merge可以合并两个已排序的序列:
cpp复制std::vector<int> result(v1.size() + v2.size());
std::merge(v1.begin(), v1.end(),
v2.begin(), v2.end(),
result.begin());
inplace_merge可以在原地合并一个序列的两个已排序部分:
cpp复制std::inplace_merge(v.begin(), v.middle(), v.end());
5. 数值算法
5.1 累计算法
accumulate是最常用的数值算法,可以计算总和或其他累积值:
cpp复制// 计算总和
int sum = std::accumulate(v.begin(), v.end(), 0);
// 计算乘积
int product = std::accumulate(v.begin(), v.end(), 1,
[](int a, int b) { return a * b; });
5.2 内积和相邻差
inner_product可以计算两个序列的内积:
cpp复制int dot_product = std::inner_product(v1.begin(), v1.end(),
v2.begin(), 0);
adjacent_difference计算相邻元素的差:
cpp复制std::adjacent_difference(v.begin(), v.end(), result.begin());
5.3 其他数值算法
partial_sum计算部分和:
cpp复制std::partial_sum(v.begin(), v.end(), result.begin());
iota可以用连续值填充序列:
cpp复制std::iota(v.begin(), v.end(), 0); // 0, 1, 2, ...
6. 算法使用技巧与性能优化
6.1 算法组合使用
STL算法的强大之处在于可以组合使用:
cpp复制// 删除所有负数和重复元素
v.erase(std::unique(
std::remove_if(v.begin(), v.end(), [](int x) {
return x < 0;
}),
v.end()),
v.end());
6.2 迭代器适配器
使用迭代器适配器可以扩展算法功能:
cpp复制// 反向遍历
std::sort(v.rbegin(), v.rend());
// 处理输出流
std::copy(v.begin(), v.end(),
std::ostream_iterator<int>(std::cout, " "));
6.3 性能考虑
选择算法时要注意时间复杂度:
- O(n)算法:find, count, accumulate
- O(n log n)算法:sort, stable_sort
- O(log n)算法:binary_search (要求已排序)
对于大型容器,应优先使用O(n)或更好的算法。
7. 常见问题与解决方案
7.1 无效迭代器问题
使用算法后迭代器可能失效:
cpp复制auto it = std::find(v.begin(), v.end(), 42);
v.insert(it, 0); // 可能导致it失效
解决方案是立即使用返回的迭代器或重新获取。
7.2 谓词设计注意事项
谓词函数应该保持纯函数特性:
cpp复制// 不好的做法:谓词有状态
int threshold = 10;
auto pred = [&threshold](int x) { return x > threshold; };
// 好的做法:谓词无状态
auto pred = [](int x, int threshold) { return x > threshold; };
7.3 容器选择影响
某些算法对容器有特殊要求:
cpp复制// list有自己的sort成员函数,比std::sort更高效
std::list<int> l;
l.sort(); // 不是std::sort(l.begin(), l.end())
8. C++17/20新增算法
8.1 并行算法
C++17引入了并行执行策略:
cpp复制std::sort(std::execution::par, v.begin(), v.end());
可用的执行策略:
- seq: 顺序执行
- par: 并行执行
- par_unseq: 并行且向量化
8.2 新算法
C++17/20新增了一些实用算法:
cpp复制// 检查所有元素是否满足条件
bool all_even = std::all_of(v.begin(), v.end(), [](int x) {
return x % 2 == 0;
});
// C++17的sample算法
std::sample(population.begin(), population.end(),
std::back_inserter(sample),
10, std::mt19937{std::random_device{}()});
9. 实际应用案例
9.1 统计单词频率
cpp复制std::map<std::string, int> word_counts;
std::for_each(words.begin(), words.end(),
[&word_counts](const std::string& word) {
++word_counts[word];
});
9.2 过滤无效数据
cpp复制data.erase(std::remove_if(data.begin(), data.end(),
[](const DataPoint& dp) {
return !dp.is_valid();
}),
data.end());
9.3 实现自定义算法
基于STL算法构建自己的算法:
cpp复制template<typename It, typename Pred>
bool contains_any_of(It first, It last, Pred pred) {
return std::find_if(first, last, pred) != last;
}
10. 性能对比与测试
10.1 算法效率比较
通过实际测试比较不同算法的性能:
| 算法 | 容器类型 | 数据规模 | 耗时(ms) |
|---|---|---|---|
| sort | vector | 1M | 120 |
| sort | deque | 1M | 150 |
| stable_sort | vector | 1M | 180 |
10.2 优化技巧
- 对于小型容器(<=64元素),简单算法可能比复杂算法更快
- 预分配足够空间避免重新分配
- 使用移动语义减少拷贝开销
cpp复制std::vector<BigObject> result;
result.reserve(input.size()); // 预分配
std::transform(input.begin(), input.end(),
std::back_inserter(result),
[](const BigObject& obj) {
return std::move(process(obj));
});
11. 扩展与自定义
11.1 编写兼容STL的算法
遵循STL约定实现自定义算法:
cpp复制template<typename InputIt, typename OutputIt, typename UnaryOp>
OutputIt transform_if(InputIt first, InputIt last,
OutputIt d_first,
UnaryOp unary_op,
Predicate pred) {
while (first != last) {
if (pred(*first)) {
*d_first++ = unary_op(*first);
}
++first;
}
return d_first;
}
11.2 迭代器类别考虑
不同算法要求不同迭代器类别:
| 算法 | 所需迭代器 |
|---|---|
| sort | 随机访问 |
| find | 输入 |
| copy | 前向 |
理解这些要求可以避免编译错误。