作为C++开发者,标准库算法是我们日常编程中不可或缺的利器。这些算法不仅能够大幅提升代码效率,还能让我们的程序更加简洁优雅。本文将系统性地介绍C++标准库中的各类算法,从基础用法到实战技巧,帮助你在实际项目中游刃有余地运用这些强大的工具。
C++标准库算法主要分为以下几大类:
理解这些分类有助于我们在面对不同需求时快速定位合适的算法。
所有标准库算法都定义在<algorithm>头文件中(数值算法在<numeric>中),使用时需要包含相应头文件。算法通常以迭代器作为参数,这使得它们可以应用于各种容器。
cpp复制#include <algorithm>
#include <vector>
std::vector<int> vec = {3, 1, 4, 1, 5, 9};
std::sort(vec.begin(), vec.end()); // 排序
find是最基础的查找算法,用于在范围内查找特定值:
cpp复制std::vector<int> nums = {1, 3, 5, 7, 9};
auto it = std::find(nums.begin(), nums.end(), 5);
if (it != nums.end()) {
std::cout << "Found: " << *it << std::endl;
}
find_if则更加灵活,可以接受谓词函数进行条件查找:
cpp复制// 查找第一个大于6的元素
auto it = std::find_if(nums.begin(), nums.end(), [](int x) {
return x > 6;
});
性能特点:这两种算法都是线性查找,时间复杂度为O(n)。对于已排序的容器,应该使用二分查找以获得更好的性能。
find_end用于查找子序列最后一次出现的位置:
cpp复制std::vector<int> main = {1,2,3,4,1,2,3};
std::vector<int> sub = {1,2};
auto it = std::find_end(main.begin(), main.end(), sub.begin(), sub.end());
search则是查找子序列第一次出现的位置,与find_end形成互补。
count统计特定值出现的次数:
cpp复制std::vector<int> vec = {1, 2, 3, 2, 4, 2};
int cnt = std::count(vec.begin(), vec.end(), 2); // 结果为3
count_if则根据谓词条件进行统计:
cpp复制int even_cnt = std::count_if(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0;
}); // 偶数个数
应用场景:这些算法在数据分析和统计中非常有用,比如统计日志中特定级别消息的数量。
for_each是对范围内每个元素应用函数的通用算法:
cpp复制std::vector<int> vec = {1, 2, 3, 4, 5};
std::for_each(vec.begin(), vec.end(), [](int& x) {
x *= 2; // 将每个元素乘以2
});
现代替代:C++11起,范围for循环通常更简洁:
cpp复制for (auto& x : vec) {
x *= 2;
}
但for_each在某些需要明确表达意图的场景下仍有优势。
equal判断两个范围是否相等:
cpp复制std::vector<int> a = {1, 2, 3};
std::vector<int> b = {1, 2, 4};
bool same = std::equal(a.begin(), a.end(), b.begin()); // false
mismatch则返回第一个不匹配的位置:
cpp复制auto mis = std::mismatch(a.begin(), a.end(), b.begin());
if (mis.first != a.end()) {
std::cout << "First mismatch: " << *mis.first << " vs " << *mis.second;
}
这些算法检查范围内元素是否满足特定条件:
cpp复制std::vector<int> vec = {2, 4, 6, 8};
bool all_even = std::all_of(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0;
}); // true
防御性编程:这些算法非常适合用于参数校验和前置条件检查。
copy是最基础的复制算法:
cpp复制std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dest(src.size());
std::copy(src.begin(), src.end(), dest.begin());
copy_if则可以选择性复制满足条件的元素:
cpp复制std::vector<int> evens;
std::copy_if(src.begin(), src.end(), std::back_inserter(evens), [](int x) {
return x % 2 == 0;
});
重要技巧:使用back_inserter可以自动处理目标容器大小问题,避免预先分配空间。
transform对元素进行转换后存储:
cpp复制std::vector<int> nums = {1, 2, 3};
std::vector<int> squares(nums.size());
std::transform(nums.begin(), nums.end(), squares.begin(), [](int x) {
return x * x;
});
双范围版本可以合并两个序列:
cpp复制std::vector<int> a = {1, 2, 3};
std::vector<int> b = {4, 5, 6};
std::vector<int> result(a.size());
std::transform(a.begin(), a.end(), b.begin(), result.begin(),
[](int x, int y) { return x + y; });
性能考虑:transform通常比手动循环更高效,编译器能更好地优化。
replace直接修改容器中的元素:
cpp复制std::vector<int> nums = {1, 2, 3, 2, 5};
std::replace(nums.begin(), nums.end(), 2, 20);
replace_if根据条件替换:
cpp复制std::replace_if(nums.begin(), nums.end(), [](int x) {
return x > 10;
}, 0);
replace_copy则在复制时替换,保持原容器不变:
cpp复制std::vector<int> res;
std::replace_copy(nums.begin(), nums.end(), std::back_inserter(res), 3, 300);
remove算法实际上并不删除元素,而是将要保留的元素前移:
cpp复制std::vector<int> nums = {1, 2, 3, 2, 4};
auto new_end = std::remove(nums.begin(), nums.end(), 2);
// nums现在为[1,3,4,2,4],new_end指向最后一个有效元素之后
要真正删除元素,需要结合erase:
cpp复制nums.erase(new_end, nums.end()); // 现在nums为[1,3,4]
常见误区:很多初学者会误以为remove已经删除了元素,实际上它只是重新排列了元素。
unique移除相邻的重复元素:
cpp复制std::vector<int> vec = {1,1,2,2,3,3,3,4,5};
auto last = std::unique(vec.begin(), vec.end());
vec.erase(last, vec.end()); // vec变为{1,2,3,4,5}
注意:unique只处理相邻元素,使用前通常需要先排序。
cpp复制std::vector<int> vec = {1, 2, 3, 4, 5};
std::reverse(vec.begin(), vec.end()); // 变为{5,4,3,2,1}
cpp复制std::vector<int> vec = {1,2,3,4,5};
std::rotate(vec.begin(), vec.begin()+2, vec.end()); // 变为{3,4,5,1,2}
cpp复制std::random_device rd;
std::mt19937 g(rd());
std::shuffle(vec.begin(), vec.end(), g);
随机数生成:使用random_device和mt19937可以获得更好的随机性。
sort是最高效的通用排序算法:
cpp复制std::vector<int> vec = {5,3,1,4,2};
std::sort(vec.begin(), vec.end()); // 升序
std::sort(vec.begin(), vec.end(), std::greater<int>()); // 降序
stable_sort保持相等元素的原始顺序:
cpp复制std::vector<std::pair<int, int>> items = {{1,2}, {2,1}, {1,1}};
std::stable_sort(items.begin(), items.end(),
[](const auto& a, const auto& b) { return a.first < b.first; });
性能对比:
sort:O(n log n),不稳定stable_sort:O(n log n)或O(n log² n),稳定partial_sort对前k个元素排序:
cpp复制std::vector<int> vec = {5,3,1,4,2,6};
std::partial_sort(vec.begin(), vec.begin()+3, vec.end());
// 前三个元素是1,2,3,其余未排序
应用场景:当只需要前几名或前几个最大/最小元素时非常高效。
nth_element重新排列使第n个元素处于正确位置:
cpp复制std::vector<int> vec = {5,3,1,4,2,6};
std::nth_element(vec.begin(), vec.begin()+2, vec.end());
// vec[2]是3,左边<=3,右边>=3
特点:时间复杂度O(n),比完整排序更高效。
cpp复制std::vector<int> sorted = {1,3,5,7,9};
bool found = std::binary_search(sorted.begin(), sorted.end(), 5); // true
前提条件:范围必须已排序,否则结果未定义。
lower_bound返回第一个不小于目标的位置:
cpp复制auto lb = std::lower_bound(sorted.begin(), sorted.end(), 3);
// lb指向第一个3
upper_bound返回第一个大于目标的位置:
cpp复制auto ub = std::upper_bound(sorted.begin(), sorted.end(), 3);
// ub指向5
组合使用:这两个算法可以用于查找元素的出现范围:
cpp复制auto bounds = std::equal_range(sorted.begin(), sorted.end(), 3);
// bounds.first等于lower_bound结果
// bounds.second等于upper_bound结果
merge合并两个已排序范围:
cpp复制std::vector<int> a = {1,3,5};
std::vector<int> b = {2,4,6};
std::vector<int> merged(a.size() + b.size());
std::merge(a.begin(), a.end(), b.begin(), b.end(), merged.begin());
应用场景:归并排序的实现基础,也常用于合并多个有序数据集。
堆是一种特殊的二叉树结构,STL提供了操作堆的算法:
cpp复制std::vector<int> vec = {4,1,3,2,5};
// 构建最大堆
std::make_heap(vec.begin(), vec.end()); // {5,4,3,2,1}
// 添加元素
vec.push_back(6);
std::push_heap(vec.begin(), vec.end()); // {6,4,5,2,1,3}
// 移除最大元素
std::pop_heap(vec.begin(), vec.end()); // 将最大元素移到末尾
int max = vec.back();
vec.pop_back();
// 堆排序
std::sort_heap(vec.begin(), vec.end()); // 转为升序
应用场景:优先级队列、Top K问题等。
cpp复制std::vector<int> vec = {1,2,3,4,5};
int sum = std::accumulate(vec.begin(), vec.end(), 0);
int product = std::accumulate(vec.begin(), vec.end(), 1,
[](int a, int b) { return a * b; });
cpp复制std::vector<int> a = {1,2,3};
std::vector<int> b = {4,5,6};
int dot = std::inner_product(a.begin(), a.end(), b.begin(), 0);
cpp复制std::vector<int> src = {1,2,3,4,5};
std::vector<int> dst(src.size());
std::partial_sum(src.begin(), src.end(), dst.begin());
// dst: {1,3,6,10,15}
cpp复制std::vector<int> src = {1,2,3,4,5};
std::vector<int> dst(src.size());
std::adjacent_difference(src.begin(), src.end(), dst.begin());
// dst: {1,1,1,1,1}
cpp复制std::vector<int> vec(5);
int n = 0;
std::generate(vec.begin(), vec.end(), [&n]() { return n++; });
// vec: {0,1,2,3,4}
cpp复制std::vector<int> vec(5);
std::iota(vec.begin(), vec.end(), 10); // {10,11,12,13,14}
cpp复制std::vector<int> a = {1,2,3,4,5};
std::vector<int> b = {2,4};
bool contains = std::includes(a.begin(), a.end(), b.begin(), b.end());
cpp复制std::vector<int> v1 = {1,2,3,4,5};
std::vector<int> v2 = {3,4,5,6,7};
std::vector<int> result;
// 并集
std::set_union(v1.begin(), v1.end(), v2.begin(), v2.end(),
std::back_inserter(result));
// 交集
result.clear();
std::set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(),
std::back_inserter(result));
// 差集
result.clear();
std::set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(),
std::back_inserter(result));
// 对称差集
result.clear();
std::set_symmetric_difference(v1.begin(), v1.end(), v2.begin(), v2.end(),
std::back_inserter(result));
选择算法时需要考虑以下因素:
stable_sort需要额外内存通用建议:
stable_sort问题1:remove为什么需要配合erase使用?
remove只是重新排列元素,返回新的逻辑终点,实际容器大小不变。erase才能真正删除元素。这是STL设计上的分离,为了效率考虑。
问题2:哪些算法需要容器已排序?
二分查找系列(binary_search、lower_bound等)、集合操作(set_union等)、merge等算法需要范围已排序。
问题3:如何选择合适的排序算法?
sort,它是最快的通用排序stable_sortpartial_sort或nth_element问题4:算法性能不理想怎么办?
C++11/14/17/20为算法库带来了许多改进:
许多算法现在支持并行执行:
cpp复制#include <execution>
std::vector<int> vec = {...};
// 并行排序
std::sort(std::execution::par, vec.begin(), vec.end());
// 并行transform
std::transform(std::execution::par,
vec.begin(), vec.end(), vec.begin(),
[](int x) { return x * 2; });
可选的执行策略:
seq:顺序执行(默认)par:并行执行par_unseq:并行且向量化范围库简化了算法调用:
cpp复制#include <ranges>
std::vector<int> vec = {1,2,3,4,5};
// 过滤偶数并平方
auto result = vec | std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * x; });
// 可以直接用于算法
bool any = std::ranges::any_of(result, [](int x) { return x > 10; });
优势:
新标准不断添加实用算法,如:
clamp(C++17):将值限制在范围内sample(C++17):随机采样shift_left/shift_right(C++20):移动元素通过组合不同算法可以解决复杂问题:
cpp复制// 删除所有满足条件的元素
vec.erase(
std::remove_if(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0;
}),
vec.end()
);
// 去重并排序
std::sort(vec.begin(), vec.end());
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
许多算法接受自定义比较函数:
cpp复制struct Person {
std::string name;
int age;
};
std::vector<Person> people = {...};
std::sort(people.begin(), people.end(),
[](const Person& a, const Person& b) { return a.age < b.age; });
注意事项:
std::tie简化比较现代C++中,lambda表达式极大增强了算法的表达能力:
cpp复制// 查找第一个年龄大于18的人
auto it = std::find_if(people.begin(), people.end(),
[](const Person& p) { return p.age > 18; });
// 统计名字长度大于5的人数
int count = std::count_if(people.begin(), people.end(),
[](const Person& p) { return p.name.size() > 5; });
back_inserter等操作,预先reserve可提高性能nth_element比完整排序更高效C++标准库算法是每个C++开发者必须掌握的核心技能。通过本文的系统介绍,你应该已经了解了:
进阶学习建议:
记住,熟练掌握这些算法不仅能提高编码效率,还能让你的代码更加简洁、高效和可维护。在实际项目中多思考如何用算法替代手动循环,这将显著提升你的代码质量。