作为C++开发者,我们每天都在与各种数据结构和算法打交道。STL(Standard Template Library)提供了一套强大而高效的算法库,可以极大地提升我们的开发效率。本文将全面剖析C++标准库中的算法,从基础用法到底层实现原理,帮助你在实际项目中游刃有余地运用这些工具。
C++标准库算法主要分为以下几大类:
理解这些分类有助于我们在面对不同需求时快速定位合适的算法。
所有STL算法都通过迭代器与容器交互,这种设计实现了算法与数据结构的解耦。主要迭代器类型包括:
不同算法对迭代器有不同的要求,理解这一点可以避免编译错误和运行时问题。
查找是最常用的操作之一,STL提供了多种查找算法:
cpp复制// find基本用法
vector<int> nums = {1, 3, 5, 7, 9};
auto it = find(nums.begin(), nums.end(), 5);
if (it != nums.end()) {
cout << "Found: " << *it << endl;
}
// 使用find_if进行条件查找
auto it2 = find_if(nums.begin(), nums.end(), [](int x) {
return x > 6;
});
cout << "First >6: " << *it2 << endl;
// 查找子序列
vector<int> sub = {3, 5};
auto it3 = find_end(nums.begin(), nums.end(), sub.begin(), sub.end());
if (it3 != nums.end()) {
cout << "Subsequence starts at index: " << it3 - nums.begin() << endl;
}
注意:find_end查找的是最后一次出现的位置,如果要找第一次出现,可以使用search算法
统计和判断是数据处理中的常见需求:
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
// 全量判断
bool all_even = all_of(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0;
}); // false
// 存在判断
bool any_odd = any_of(vec.begin(), vec.end(), [](int x) {
return x % 2 != 0;
}); // true
// 不存在判断
bool none_negative = none_of(vec.begin(), vec.end(), [](int x) {
return x < 0;
}); // true
for_each和比较算法在日常开发中非常实用:
cpp复制// for_each应用
vector<int> vec = {1, 2, 3, 4, 5};
for_each(vec.begin(), vec.end(), [](int& x) {
x *= 2;
});
// vec变为{2, 4, 6, 8, 10}
// 序列比较
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 << "Mismatch at: " << *mis.first << " vs " << *mis.second << endl;
}
复制和变换是数据处理的基础操作:
cpp复制// 基本复制
vector<int> src = {1, 2, 3, 4, 5};
vector<int> dest(5);
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]
// 元素变换
vector<int> squares(src.size());
transform(src.begin(), src.end(), squares.begin(), [](int x) {
return x * x;
});
// squares: [1,4,9,16,25]
// 双序列变换
vector<int> a = {1, 2, 3};
vector<int> b = {4, 5, 6};
vector<int> sum(3);
transform(a.begin(), a.end(), b.begin(), sum.begin(), [](int x, int y) {
return x + y;
});
// sum: [5,7,9]
提示:使用back_inserter可以避免预先分配空间,但频繁push_back可能导致多次内存重分配
替换和删除操作需要注意一些细节:
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> res;
replace_copy(nums.begin(), nums.end(), back_inserter(res), 3, 300);
// res: [1,0,300,0,5], nums不变
// 删除元素
nums = {1, 2, 3, 2, 4};
auto new_end = remove(nums.begin(), nums.end(), 2);
// nums逻辑变为[1,3,4,2,2], new_end指向第一个2
nums.erase(new_end, nums.end());
// nums: [1,3,4]
// 条件删除
nums.erase(remove_if(nums.begin(), nums.end(), [](int x) {
return x % 2 == 0;
}), nums.end());
// nums: [1,3]
处理数据时经常需要去重和重新排列:
cpp复制// 去重
vector<int> vec = {1, 1, 2, 2, 3, 3, 3, 4, 5};
auto last = unique(vec.begin(), vec.end());
vec.erase(last, vec.end());
// vec: [1,2,3,4,5]
// 反转
reverse(vec.begin(), vec.end());
// vec: [5,4,3,2,1]
// 旋转
vector<int> rot = {1, 2, 3, 4, 5};
rotate(rot.begin(), rot.begin() + 2, rot.end());
// rot: [3,4,5,1,2]
// 随机重排
random_device rd;
mt19937 g(rd());
shuffle(rot.begin(), rot.end(), g);
// rot被随机打乱
STL提供了多种排序算法,各有特点:
cpp复制vector<int> vec = {5, 3, 1, 4, 2};
// 快速排序(不稳定)
sort(vec.begin(), vec.end());
// vec: [1,2,3,4,5]
// 稳定排序
vector<pair<int, int>> pairs = {{1,2}, {2,1}, {1,1}, {2,2}};
stable_sort(pairs.begin(), pairs.end(), [](const auto& a, const auto& b) {
return a.first < b.first;
});
// 保持相同first元素的相对顺序
// 部分排序
vector<int> nums = {5, 3, 1, 4, 2, 6};
partial_sort(nums.begin(), nums.begin() + 3, nums.end());
// 前三个是最小的有序元素:[1,2,3], 后面无序
// 选择第n小元素
nth_element(nums.begin(), nums.begin() + 2, nums.end());
// nums[2]是第三小的元素,左右分别<=和>=
二分查找要求序列已排序:
cpp复制vector<int> sorted = {1, 3, 3, 5, 7};
// 存在判断
bool exists = binary_search(sorted.begin(), sorted.end(), 3); // true
// 下界和上界
auto lb = lower_bound(sorted.begin(), sorted.end(), 3); // 第一个>=3的元素
auto ub = upper_bound(sorted.begin(), sorted.end(), 3); // 第一个>3的元素
// 合并有序序列
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());
// merged: [1,2,3,4,5,6]
堆是一种重要的数据结构,STL提供了完整支持:
cpp复制vector<int> vec = {4, 1, 3, 2, 5};
// 建堆
make_heap(vec.begin(), vec.end());
// vec: [5,4,3,2,1] (最大堆)
// 添加元素
vec.push_back(6);
push_heap(vec.begin(), vec.end());
// vec: [6,4,5,2,1,3]
// 弹出堆顶
pop_heap(vec.begin(), vec.end());
int max_val = vec.back(); // 6
vec.pop_back();
// 堆排序
sort_heap(vec.begin(), vec.end());
// vec: [1,2,3,4,5]
cpp复制vector<int> vec = {1, 2, 3, 4, 5};
// 累加
int sum = accumulate(vec.begin(), vec.end(), 0); // 15
// 内积
vector<int> a = {1, 2, 3};
vector<int> b = {4, 5, 6};
int dot = inner_product(a.begin(), a.end(), b.begin(), 0); // 32
// 填充序列值
vector<int> seq(5);
iota(seq.begin(), seq.end(), 10); // [10,11,12,13,14]
// 部分和
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]
// 相邻差值
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++; });
// vec: [0,1,2,3,4]
// 生成前n个
generate_n(vec.begin(), 3, [&n]() { return n++; });
// 前三个变为[5,6,7]
集合操作要求输入已排序:
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));
// result: [1,2,3,4,5,6,7]
// 交集
result.clear();
set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), back_inserter(result));
// result: [3,4,5]
// 差集
result.clear();
set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), back_inserter(result));
// result: [1,2]
// 对称差集
result.clear();
set_symmetric_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), back_inserter(result));
// result: [1,2,6,7]
选择算法时需要考虑以下因素:
实际开发中的经验法则:
Q:sort和stable_sort有什么区别?
A:sort通常使用快速排序的变种(introsort),不稳定但平均性能好;stable_sort使用归并排序,稳定但需要额外空间。
Q:remove为什么需要配合erase使用?
A:remove只是将不需要的元素移动到末尾并返回新的逻辑终点,实际容器大小不变。erase才能真正删除这些元素。
案例1:统计日志中出现频率最高的IP
cpp复制vector<string> ips = {...}; // 日志IP
unordered_map<string, int> counts;
for_each(ips.begin(), ips.end(), [&counts](const string& ip) {
counts[ip]++;
});
auto max_it = max_element(counts.begin(), counts.end(),
[](const auto& a, const auto& b) { return a.second < b.second; });
案例2:合并两个有序的用户列表
cpp复制vector<User> users1 = {...}; // 已排序
vector<User> users2 = {...}; // 已排序
vector<User> merged;
merge(users1.begin(), users1.end(),
users2.begin(), users2.end(),
back_inserter(merged),
[](const User& a, const User& b) { return a.id < b.id; });
案例3:快速选择前10%的高分学生
cpp复制vector<Student> students = {...};
auto top10 = students.begin() + students.size()/10;
nth_element(students.begin(), top10, students.end(),
[](const Student& a, const Student& b) { return a.score > b.score; });
// 前10%的学生现在在[begin,top10)区间
掌握STL算法不仅能提高编码效率,还能写出更简洁、更易维护的代码。建议在实际项目中多思考如何用算法替代手写循环,这往往能带来意想不到的优化效果。