作为一名有着十年C++开发经验的老手,我经常看到新手开发者重复造轮子,手动实现那些标准库已经完美提供的算法功能。今天我就来系统梳理一下C++标准库中最实用的算法,这些内容都是我多年实战中总结出来的精华。
C++标准库算法主要定义在
查找算法是日常开发中使用频率最高的一类,掌握它们能极大提升编码效率。
cpp复制vector<int> nums = {1, 3, 5, 7, 9};
// 查找值为5的元素
auto it = find(nums.begin(), nums.end(), 5);
if (it != nums.end()) {
cout << "Found: " << *it << endl;
}
// 查找第一个大于6的元素
auto it2 = find_if(nums.begin(), nums.end(), [](int x) {
return x > 6;
});
实际项目中,我经常用find_if来查找符合特定业务条件的对象。比如在一个员工列表中查找年龄大于30且薪资低于10000的员工。
cpp复制vector<int> main = {1,2,3,4,1,2,3};
vector<int> sub = {1,2};
auto it = find_end(main.begin(), main.end(), sub.begin(), sub.end());
// 找到最后一个{1,2}子序列的位置
find_end适合在日志分析中查找特定的错误模式最后一次出现的位置。
cpp复制vector<int> vec = {1, 2, 3, 2, 4, 2};
int cnt = count(vec.begin(), vec.end(), 2); // 3
int even_cnt = count_if(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0;
}); // 4
在统计分析中,我常用count_if来计算满足特定业务指标的数据量,比如统计订单系统中金额大于1000的订单数量。
cpp复制vector<int> vec = {1, 2, 3, 4, 5};
for_each(vec.begin(), vec.end(), [](int& x) {
x *= 2;
});
for_each是我最喜欢用的算法之一,它比传统for循环更简洁,且能明确表达"对每个元素做某事"的意图。
cpp复制vector<int> a = {1, 2, 3};
vector<int> b = {1, 2, 4};
bool is_equal = equal(a.begin(), a.end(), b.begin()); // false
auto mis = mismatch(a.begin(), a.end(), b.begin());
if (mis.first != a.end()) {
cout << "First mismatch: " << *mis.first << " vs " << *mis.second << endl;
}
在单元测试中,我常用equal来验证两个数据集的正确性,用mismatch来定位第一个不一致的位置。
cpp复制vector<int> vec = {2, 4, 6, 8};
bool all_even = all_of(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0;
}); // true
这些算法在验证输入数据有效性时非常有用,比如检查用户提交的表单是否所有字段都符合要求。
cpp复制vector<int> src = {1, 2, 3, 4, 5};
vector<int> dest(src.size());
copy(src.begin(), src.end(), dest.begin());
vector<int> evens;
copy_if(src.begin(), src.end(), back_inserter(evens), [](int x) {
return x % 2 == 0;
});
重要提示:使用back_inserter时不需要预先分配空间,它会自动调用push_back
在数据处理流水线中,我常用copy_if来过滤不需要的数据,只保留感兴趣的部分。
cpp复制vector<int> nums = {1, 2, 3};
vector<int> squares(nums.size());
transform(nums.begin(), nums.end(), squares.begin(), [](int x) {
return x * x;
});
// 两序列操作
vector<int> a = {1, 2, 3};
vector<int> b = {4, 5, 6};
vector<int> sum(a.size());
transform(a.begin(), a.end(), b.begin(), sum.begin(), plus<int>());
transform是函数式编程风格的重要体现,我经常用它来实现数据转换流水线。
cpp复制vector<int> nums = {1, 2, 3, 2, 5};
replace(nums.begin(), nums.end(), 2, 20);
replace_if(nums.begin(), nums.end(), [](int x) {
return x > 10;
}, 0);
vector<int> res;
replace_copy(nums.begin(), nums.end(), back_inserter(res), 3, 300);
在数据清洗过程中,我常用replace_if来替换异常值为合理的默认值。
cpp复制vector<int> nums = {1, 2, 3, 2, 4};
auto new_end = remove(nums.begin(), nums.end(), 2);
nums.erase(new_end, nums.end());
nums.erase(remove_if(nums.begin(), nums.end(), [](int x) {
return x % 2 == 0;
}), nums.end());
关键理解:remove只是把要删除的元素移到末尾,返回新的逻辑终点,必须配合erase才能真正删除
这是C++算法设计中一个常见的习惯用法,称为"erase-remove"惯用法,我在处理用户输入时经常使用。
cpp复制vector<int> vec = {1, 1, 2, 2, 3, 3, 3, 4, 5};
vec.erase(unique(vec.begin(), vec.end()), vec.end());
unique通常用于处理从数据库查询出来的结果,去除连续的重复记录。
cpp复制vector<int> vec = {1, 2, 3, 4, 5};
reverse(vec.begin(), vec.end());
cpp复制vector<int> vec = {1, 2, 3, 4, 5};
rotate(vec.begin(), vec.begin() + 2, vec.end());
// 变为{3,4,5,1,2}
cpp复制vector<int> vec = {1, 2, 3, 4, 5};
random_device rd;
mt19937 g(rd());
shuffle(vec.begin(), vec.end(), g);
在开发游戏时,我常用shuffle来随机打乱卡牌或题目顺序。
cpp复制vector<int> vec = {5, 3, 1, 4, 2};
sort(vec.begin(), vec.end()); // 升序
sort(vec.begin(), vec.end(), greater<int>()); // 降序
vector<pair<int, int>> pairs = {{1,2}, {2,1}, {1,1}};
stable_sort(pairs.begin(), pairs.end()); // 保持相等元素的原始顺序
性能提示:sort平均O(n log n),但不稳定;stable_sort保证稳定但稍慢
在需要显示排序结果时,我通常会根据是否需要保持相等元素的原始顺序来选择使用sort还是stable_sort。
cpp复制vector<int> vec = {5, 3, 1, 4, 2, 6};
partial_sort(vec.begin(), vec.begin() + 3, vec.end());
// 前三个元素是排序后的最小三个值
当只需要前N个有序元素时,partial_sort比完全排序更高效,我在实现排行榜功能时经常使用。
cpp复制vector<int> vec = {5, 3, 1, 4, 2, 6};
nth_element(vec.begin(), vec.begin() + 2, vec.end());
// vec[2]是第3小的元素,左边<=它,右边>=它
nth_element适合找中位数或任意百分位的值,我在数据分析中经常使用。
cpp复制vector<int> sorted = {1, 3, 3, 5, 7};
auto lb = lower_bound(sorted.begin(), sorted.end(), 3); // 第一个>=3的
auto ub = upper_bound(sorted.begin(), sorted.end(), 3); // 第一个>3的
这两个算法在实现范围查询时非常有用,比如查找某个时间段内的所有日志记录。
cpp复制vector<int> a = {1, 3, 5};
vector<int> b = {2, 4, 6};
vector<int> merged(a.size() + b.size());
merge(a.begin(), a.end(), b.begin(), b.end(), merged.begin());
merge是归并排序的核心操作,我在处理多个有序数据源时经常使用。
STL提供了一套完整的堆操作算法,可以方便地实现优先队列。
cpp复制vector<int> vec = {4, 1, 3, 2, 5};
make_heap(vec.begin(), vec.end()); // 构建最大堆
vec.push_back(6);
push_heap(vec.begin(), vec.end()); // 调整堆
pop_heap(vec.begin(), vec.end()); // 将最大元素移到末尾
int max_val = vec.back();
vec.pop_back();
sort_heap(vec.begin(), vec.end()); // 堆排序
在实际项目中,我常用这些堆算法来实现任务调度系统,总是优先执行优先级最高的任务。
cpp复制int a = 5, b = 3;
int min_val = min(a, b);
int max_val = max(a, b);
auto min_of_list = min({4, 2, 8, 5, 1});
auto max_of_list = max({4, 2, 8, 5, 1});
cpp复制vector<int> vec = {3, 1, 4, 2, 5};
auto min_it = min_element(vec.begin(), vec.end());
auto max_it = max_element(vec.begin(), vec.end());
cpp复制auto minmax = minmax_element(vec.begin(), vec.end());
// minmax.first指向最小值,minmax.second指向最大值
这些算法在统计分析中非常有用,比如计算数据集的范围。
cpp复制vector<int> vec = {1, 2, 3, 4, 5};
int sum = accumulate(vec.begin(), vec.end(), 0);
int product = accumulate(vec.begin(), vec.end(), 1, multiplies<int>());
accumulate非常灵活,通过提供自定义操作,可以实现各种累加计算,比如计算加权平均值。
cpp复制vector<int> a = {1, 2, 3};
vector<int> b = {4, 5, 6};
int dot = inner_product(a.begin(), a.end(), b.begin(), 0);
inner_product不仅可以计算点积,通过自定义操作还能实现各种向量运算。
cpp复制vector<int> vec(5);
iota(vec.begin(), vec.end(), 10); // 10,11,12,13,14
iota在生成测试数据时非常方便,可以快速创建连续的数值序列。
cpp复制vector<int> src = {1, 2, 3, 4, 5};
vector<int> dst(src.size());
partial_sum(src.begin(), src.end(), dst.begin()); // 1,3,6,10,15
partial_sum在计算累计分布时很有用,比如计算销售额的累计百分比。
cpp复制vector<int> src = {1, 2, 3, 4, 5};
vector<int> dst(src.size());
adjacent_difference(src.begin(), src.end(), dst.begin()); // 1,1,1,1,1
这个算法适合计算时间序列数据的变化量,比如每日温度变化。
cpp复制vector<int> vec(5);
int n = 0;
generate(vec.begin(), vec.end(), [&n]() { return n++; });
generate在初始化测试数据时非常有用,可以避免手动填充数组。
cpp复制vector<int> vec1 = {1, 2, 3, 4, 5};
vector<int> vec2 = {2, 4};
bool includes = includes(vec1.begin(), vec1.end(), vec2.begin(), vec2.end());
cpp复制vector<int> v1 = {1, 2, 3, 4, 5};
vector<int> v2 = {3, 4, 5, 6, 7};
vector<int> result;
set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), back_inserter(result));
set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), back_inserter(result));
set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), back_inserter(result));
set_symmetric_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), back_inserter(result));
这些集合操作在数据库查询优化和数据分析中非常有用,可以高效地实现各种集合运算。
sort与stable_sort如何选择?
为什么remove不真正删除元素?
哪些算法要求输入已排序?
避免不必要的拷贝
预分配内存
选择合适的算法
算法组合使用
自定义比较函数
并行算法(C++17)
C++11/14/17/20为算法库带来了许多改进:
cpp复制vector<int> vec = {...};
sort(execution::par, vec.begin(), vec.end());
constexpr算法(C++20)
许多算法现在可以在编译期执行
范围算法(C++20)
cpp复制vector<int> vec = {...};
auto result = ranges::sort(vec);
这些新特性让标准库算法更加强大和易用,建议在支持新标准的项目中优先使用。