1. C++标准库算法概览
作为一名有着十年C++开发经验的老手,我经常看到新手开发者重复造轮子——手写各种查找、排序算法。实际上,C++标准库提供了丰富高效的算法,可以覆盖90%的日常需求。这些算法主要定义在<algorithm>和<numeric>头文件中,按照功能可以分为非修改序列算法、修改序列算法、排序相关算法、堆算法和数值算法等几大类。
标准库算法的优势在于:
- 经过严格测试和优化,性能有保障
- 接口统一,使用方便
- 与STL容器无缝配合
- 支持自定义比较函数和谓词,灵活性强
理解这些算法的特性和适用场景,能显著提升开发效率和代码质量。下面我将结合实例详细介绍各类常用算法。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 非修改序列算法
2.1 查找算法
find与find_if
find是最基础的线性查找算法,时间复杂度O(n):
cpp复制vector<int> nums = {1, 3, 5, 7, 9};
auto it = find(nums.begin(), nums.end(), 5);
if (it != nums.end()) {
cout << "Found at index: " << distance(nums.begin(), it);
}
find_if则支持自定义条件查找:
cpp复制// 查找第一个大于6的元素
auto it = find_if(nums.begin(), nums.end(), [](int x) {
return x > 6;
});
提示:对于已排序的容器,应优先使用
lower_bound等二分查找算法,时间复杂度O(log n)
find_end与search
find_end用于查找子序列最后一次出现的位置:
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());
// it指向第4个元素(index 4)
search则是查找子序列第一次出现的位置,参数与find_end相同。
2.2 计数算法
count与count_if
cpp复制vector<int> vec = {1, 2, 2, 3, 2, 4};
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
2.3 遍历算法for_each
for_each是对容器元素进行操作的基础方法:
cpp复制vector<int> vec = {1, 2, 3};
for_each(vec.begin(), vec.end(), [](int& x) {
x *= 2;
});
// vec变为{2, 4, 6}
注意:C++11起可以直接用范围for循环,但for_each的优势在于可以明确指定操作范围
2.4 比较算法
equal与mismatch
cpp复制vector<int> a = {1,2,3};
vector<int> b = {1,2,4};
bool same = equal(a.begin(), a.end(), b.begin()); // false
auto pair = mismatch(a.begin(), a.end(), b.begin());
// pair.first指向a的3,pair.second指向b的4
all_of/any_of/none_of
检查元素是否全部/存在/没有满足条件:
cpp复制vector<int> vec = {2,4,6};
bool allEven = all_of(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0;
}); // true
3. 修改序列算法
3.1 复制算法
copy与copy_if
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;
});
// evens: {2,4}
技巧:使用
back_inserter可以自动处理目标容器空间不足的情况
3.2 变换算法transform
cpp复制vector<int> nums = {1,2,3};
vector<int> squares(nums.size());
transform(nums.begin(), nums.end(), squares.begin(), [](int x) {
return x * x;
});
// squares: {1,4,9}
// 两个序列的transform
vector<int> a = {1,2,3};
vector<int> b = {4,5,6};
vector<int> result(a.size());
transform(a.begin(), a.end(), b.begin(), result.begin(), plus<int>());
// result: {5,7,9}
3.3 替换算法
replace系列
cpp复制vector<int> nums = {1,2,3,2,5};
replace(nums.begin(), nums.end(), 2, 20);
// nums: {1,20,3,20,5}
replace_if(nums.begin(), nums.end(), [](int x) {
return x > 10;
}, 0);
// nums: {1,0,3,0,5}
vector<int> src = {1,2,3};
vector<int> dest;
replace_copy(src.begin(), src.end(), back_inserter(dest), 2, 20);
// src不变,dest: {1,20,3}
3.4 删除算法
remove与erase
这是新手最容易误解的算法组合:
cpp复制vector<int> nums = {1,2,3,2,4};
auto new_end = remove(nums.begin(), nums.end(), 2);
// nums变为{1,3,4,2,4},new_end指向第3个元素
nums.erase(new_end, nums.end());
// nums: {1,3,4}
关键点:remove只是把要保留的元素前移,返回新的逻辑终点,必须配合erase才能真正删除元素
unique去重
cpp复制vector<int> nums = {1,1,2,2,3,3};
auto last = unique(nums.begin(), nums.end());
nums.erase(last, nums.end());
// nums: {1,2,3}
注意:unique只去除相邻的重复元素,使用前通常需要先排序
4. 排序及相关算法
4.1 基础排序
sort与stable_sort
cpp复制vector<int> vec = {5,3,1,4,2};
sort(vec.begin(), vec.end()); // 默认升序
sort(vec.begin(), vec.end(), greater<int>()); // 降序
vector<Person> people;
stable_sort(people.begin(), people.end(), [](const Person& a, const Person& b) {
return a.age < b.age; // 保持相同年龄元素的原始顺序
});
partial_sort部分排序
cpp复制vector<int> vec = {5,3,1,4,2,6};
partial_sort(vec.begin(), vec.begin()+3, vec.end());
// 前3个元素是{1,2,3},后面未排序
4.2 二分查找
要求容器必须已排序:
cpp复制vector<int> vec = {1,3,3,5,7};
bool found = binary_search(vec.begin(), vec.end(), 3); // true
auto lb = lower_bound(vec.begin(), vec.end(), 3); // 第一个>=3的元素
auto ub = upper_bound(vec.begin(), vec.end(), 3); // 第一个>3的元素
// 可用equal_range获取等于某值的范围
4.3 合并算法merge
cpp复制vector<int> a = {1,3,5};
vector<int> b = {2,4,6};
vector<int> result(a.size() + b.size());
merge(a.begin(), a.end(), b.begin(), b.end(), result.begin());
// result: {1,2,3,4,5,6}
5. 堆算法
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 = vec.back();
vec.pop_back();
sort_heap(vec.begin(), vec.end()); // 堆排序
6. 数值算法
6.1 累加算法accumulate
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>());
6.2 内积算法inner_product
cpp复制vector<int> a = {1,2,3};
vector<int> b = {4,5,6};
int dot = inner_product(a.begin(), a.end(), b.begin(), 0);
// 1*4 + 2*5 + 3*6 = 32
6.3 相邻差值adjacent_difference
cpp复制vector<int> src = {1,2,4,7,11};
vector<int> dst(src.size());
adjacent_difference(src.begin(), src.end(), dst.begin());
// dst: {1,1,2,3,4}
7. 实战经验与陷阱
- 迭代器失效问题:在循环中修改容器时,可能导致迭代器失效。例如:
cpp复制for(auto it = vec.begin(); it != vec.end(); ) {
if(*it % 2 == 0) {
it = vec.erase(it); // erase返回下一个有效迭代器
} else {
++it;
}
}
- 谓词的设计:谓词函数应该保持纯洁性,避免修改元素或依赖外部状态:
cpp复制// 不好的做法
int threshold = 5;
auto pred = [&](int x) {
threshold--; // 修改外部状态
return x > threshold;
};
// 好的做法
auto pred = [](int x) {
return x % 2 == 0; // 无副作用
};
-
性能考量:
- 对小数据集(如n<20),简单算法如冒泡排序可能更快
- 对基本类型,
sort通常比stable_sort快 - 频繁插入删除的场景考虑使用
list而非vector
-
C++17新特性:
sample:从范围中随机采样clamp:将值限制在范围内gcd/lcm:最大公约数和最小公倍数
8. 算法选择指南
根据常见需求推荐算法:
| 需求 | 推荐算法 | 备注 |
|---|---|---|
| 查找元素 | find/find_if | 无序序列使用 |
| 二分查找 | lower_bound | 要求序列已排序 |
| 排序 | sort | 默认快速排序 |
| 稳定排序 | stable_sort | 保持相等元素顺序 |
| 删除元素 | remove+erase | 经典组合 |
| 去重 | unique+erase | 通常先排序 |
| 遍历修改 | transform | 比for_each更函数式 |
| 累加 | accumulate | 支持自定义操作 |
掌握这些标准算法后,你会发现很多业务逻辑都可以用现有算法组合实现,无需重复造轮子。这不仅提高开发效率,还能保证代码的性能和可靠性。
