C++标准库提供了丰富的算法,主要定义在<algorithm>和<numeric>头文件中。这些算法可以大大简化日常编程工作,避免重复造轮子。根据功能特点,我们可以将这些算法分为以下几类:
这些算法大多以迭代器作为参数,因此可以适用于各种容器,具有很高的通用性。掌握这些算法能显著提高代码质量和开发效率。
查找算法是日常开发中最常用的算法之一,主要包括以下几种:
find用于查找特定值,find_if则可以使用谓词进行条件查找:
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; // 输出:5
}
// 查找第一个大于6的元素
auto it2 = find_if(nums.begin(), nums.end(), [](int x) {
return x > 6;
});
cout << "first >6: " << *it2 << endl; // 输出:7
注意:
find和find_if都是线性查找,时间复杂度为O(n)。对于已排序的容器,应该使用二分查找以获得更好的性能。
查找子序列最后一次出现的位置:
cpp复制vector<int> nums = {1, 3, 5, 3, 5, 7};
vector<int> sub = {3, 5};
auto it = find_end(nums.begin(), nums.end(), sub.begin(), sub.end());
if (it != nums.end()) {
cout << "subsequence starts at index: " << it - nums.begin() << endl; // 输出:3
}
count统计特定值的出现次数,count_if则可以使用谓词进行条件统计:
cpp复制vector<int> vec = {1, 2, 3, 2, 4, 2};
int cnt = count(vec.begin(), vec.end(), 2); // 计数2的个数,结果为3
int even_cnt = count_if(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0;
}); // 偶数个数,结果为4
对范围内的每个元素应用一个函数:
cpp复制vector<int> vec = {1, 2, 3, 4, 5};
for_each(vec.begin(), vec.end(), [](int& x) {
x *= 2; // 将每个元素乘以2
});
// 现在vec变为{2, 4, 6, 8, 10}
提示:C++17引入了
for_each_n,可以指定处理前n个元素。
equal判断两个范围是否相等,mismatch返回第一个不匹配的位置:
cpp复制vector<int> a = {1, 2, 3};
vector<int> b = {1, 2, 4};
vector<int> c = {1, 2, 3, 4};
bool is_equal = equal(a.begin(), a.end(), b.begin()); // false
auto mis = mismatch(a.begin(), a.end(), c.begin());
if (mis.first == a.end()) {
cout << "no mismatch in first " << a.size() << " elements" << endl;
}
检查范围内元素是否满足特定条件:
cpp复制vector<int> vec = {2, 4, 6, 8};
bool all_even = all_of(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0;
}); // true
bool any_odd = any_of(vec.begin(), vec.end(), [](int x) {
return x % 2 != 0;
}); // false
bool none_negative = none_of(vec.begin(), vec.end(), [](int x) {
return x < 0;
}); // true
copy复制整个范围,copy_if只复制满足条件的元素:
cpp复制vector<int> src = {1, 2, 3, 4, 5};
vector<int> dest(5); // 需预先分配足够空间
copy(src.begin(), src.end(), dest.begin()); // dest: [1,2,3,4,5]
vector<int> evens;
copy_if(src.begin(), src.end(), back_inserter(evens), [](int x) {
return x % 2 == 0;
}); // evens: [2,4]
技巧:使用
back_inserter可以自动扩展容器,无需预先分配空间。
对元素进行转换并存储结果:
cpp复制vector<int> nums = {1, 2, 3};
vector<int> squares(3);
transform(nums.begin(), nums.end(), squares.begin(), [](int x) {
return x * x;
}); // squares: [1,4,9]
// 两容器元素相加
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(), plus<int>()); // sum: [5,7,9]
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]
cpp复制vector<int> nums = {1, 2, 3, 2, 4};
auto new_end = remove(nums.begin(), nums.end(), 2); // nums: [1,3,4,2,2]
nums.erase(new_end, nums.end()); // nums: [1,3,4]
// 结合lambda删除偶数
nums = {1, 2, 3, 4, 5};
nums.erase(remove_if(nums.begin(), nums.end(), [](int x) {
return x % 2 == 0;
}), nums.end()); // nums: [1,3,5]
重要:
remove只是将不需要的元素移动到末尾,并没有真正删除,必须配合erase使用。
去除连续重复元素:
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}
反转元素顺序:
cpp复制vector<int> vec = {1, 2, 3, 4, 5};
reverse(vec.begin(), vec.end()); // vec变为{5, 4, 3, 2, 1}
旋转元素:
cpp复制vector<int> vec = {1, 2, 3, 4, 5};
rotate(vec.begin(), vec.begin() + 2, vec.end()); // 以3为起点旋转,vec变为{3, 4, 5, 1, 2}
随机重排:
cpp复制#include <random>
#include <algorithm>
vector<int> vec = {1, 2, 3, 4, 5};
random_device rd;
mt19937 g(rd());
shuffle(vec.begin(), vec.end(), g); // 随机打乱vec中的元素
cpp复制vector<int> vec = {5, 3, 1, 4, 2};
sort(vec.begin(), vec.end()); // 默认升序,vec变为{1, 2, 3, 4, 5}
sort(vec.begin(), vec.end(), greater<int>()); // 降序,vec变为{5, 4, 3, 2, 1}
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排序,保持相等元素的相对顺序
});
部分排序:
cpp复制vector<int> vec = {5, 3, 1, 4, 2, 6};
// 将最小的3个元素放在前面并排序
partial_sort(vec.begin(), vec.begin() + 3, vec.end());
// 现在vec前三个元素是1, 2, 3,后面是未排序的4, 5, 6
cpp复制vector<int> vec = {5, 3, 1, 4, 2, 6};
// 找到第三小的元素(索引2)
nth_element(vec.begin(), vec.begin() + 2, vec.end());
// 现在vec[2]是3,它左边的元素<=3,右边的>=3
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的元素
cout << "3 appears " << ub - lb << " times" << endl; // 输出:2
合并两个已排序的范围:
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()); // 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()); // 将最大元素移到末尾,vec变为{5, 4, 3, 2, 1, 6}
int max_val = vec.back(); // 获取最大元素6
vec.pop_back(); // 移除最大元素
sort_heap(vec.begin(), vec.end()); // 将堆排序为升序序列,vec变为{1, 2, 3, 4, 5}
累加或自定义操作:
cpp复制#include <numeric>
vector<int> vec = {1, 2, 3, 4, 5};
int sum = accumulate(vec.begin(), vec.end(), 0); // 和,初始值为0,结果为15
int product = accumulate(vec.begin(), vec.end(), 1, multiplies<int>()); // 乘积,初始值为1,结果为120
内积计算:
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
填充递增序列:
cpp复制vector<int> vec(5);
iota(vec.begin(), vec.end(), 10); // 填充为10, 11, 12, 13, 14
部分和:
cpp复制vector<int> src = {1, 2, 3, 4, 5};
vector<int> dst(src.size());
partial_sum(src.begin(), src.end(), dst.begin()); // dst变为{1, 3, 6, 10, 15}
相邻差值:
cpp复制vector<int> src = {1, 2, 3, 4, 5};
vector<int> dst(src.size());
adjacent_difference(src.begin(), src.end(), dst.begin()); // dst变为{1, 1, 1, 1, 1}
cpp复制vector<int> vec(5);
int n = 0;
generate(vec.begin(), vec.end(), [&n]() {
return n++;
}); // 填充为0, 1, 2, 3, 4
generate_n(vec.begin(), 3, [&n]() {
return n++;
}); // 前三个元素为4, 5, 6,后两个保持不变
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}
查找算法选择:
find、find_ifbinary_search、lower_bound、upper_boundsearch、find_end排序算法选择:
sortstable_sortpartial_sort、nth_element删除元素:
erase-remove惯用法remove不会改变容器大小,必须配合erase避免不必要的拷贝:
transform代替循环+拷贝预分配内存:
copy、transform等算法,预分配目标容器空间back_inserter让算法自动处理算法组合:
sort+unique+erase去重remove_if+erase删除满足条件的元素为什么sort比stable_sort快?
sort通常使用快速排序的变种(introsort),不需要保持相等元素的相对顺序,因此更快且使用更少内存。stable_sort通常使用归并排序,需要额外空间来保持稳定性。
remove为什么不能直接删除元素?
remove算法只通过迭代器访问元素,不知道如何从容器中真正删除元素(这需要容器自身的成员函数)。因此它只是将要删除的元素移动到末尾,返回新的逻辑终点,由调用者决定如何真正删除。
如何选择合适的排序算法?
sort,除非需要稳定性stable_sortpartial_sortnth_element为什么二分查找算法要求容器已排序?
二分查找的核心思想是每次比较都能排除一半的数据。如果数据无序,这种排除就无法保证,算法将退化为线性查找。因此必须预先排序才能保证二分查找的正确性和效率。
如何高效去重?
标准做法是先排序,再使用unique+erase:
cpp复制vector<int> vec = {1, 2, 2, 3, 3, 3, 4, 5};
sort(vec.begin(), vec.end());
vec.erase(unique(vec.begin(), vec.end()), vec.end());
C++11/14/17/20对算法库进行了多项增强:
许多算法支持并行执行:
cpp复制#include <execution>
vector<int> vec = {...};
// 并行排序
sort(execution::par, vec.begin(), vec.end());
// 并行transform
vector<int> result(vec.size());
transform(execution::par, vec.begin(), vec.end(), result.begin(), [](int x) {
return x * x;
});
支持的执行策略:
execution::seq - 顺序执行execution::par - 并行执行execution::par_unseq - 并行+向量化执行clamp(C++17):将值限制在范围内sample(C++17):随机采样for_each_n(C++17):对前N个元素应用函数shift_left/shift_right(C++20):移动元素C++20引入了范围库,提供更简洁的算法调用方式:
cpp复制#include <ranges>
vector<int> vec = {1, 2, 3, 4, 5};
// 过滤偶数并平方
auto result = vec | views::filter([](int x) { return x % 2 == 0; })
| views::transform([](int x) { return x * x; });
for (auto x : result) {
cout << x << " "; // 输出:4 16
}
cpp复制#include <algorithm>
#include <map>
#include <vector>
#include <string>
vector<string> words = {"apple", "banana", "apple", "cherry", "banana", "apple"};
map<string, int> word_counts;
for (const auto& word : words) {
word_counts[word]++;
}
// 按频率排序
vector<pair<string, int>> sorted_counts(word_counts.begin(), word_counts.end());
sort(sorted_counts.begin(), sorted_counts.end(), [](const auto& a, const auto& b) {
return a.second > b.second;
});
// 输出结果
for (const auto& [word, count] : sorted_counts) {
cout << word << ": " << count << endl;
}
cpp复制vector<int> a = {1, 2, 3, 4, 5};
vector<int> b = {3, 4, 5, 6, 7};
// 先排序
sort(a.begin(), a.end());
sort(b.begin(), b.end());
vector<int> intersection;
set_intersection(a.begin(), a.end(), b.begin(), b.end(), back_inserter(intersection));
// intersection: {3, 4, 5}
cpp复制struct Person {
string name;
int age;
};
vector<Person> people = {{"Alice", 25}, {"Bob", 20}, {"Charlie", 30}};
// 按年龄排序
sort(people.begin(), people.end(), [](const Person& a, const Person& b) {
return a.age < b.age;
});
// 按名字长度排序
sort(people.begin(), people.end(), [](const Person& a, const Person& b) {
return a.name.size() < b.name.size();
});
| 算法 | 平均时间复杂度 | 备注 |
|---|---|---|
| find | O(n) | 线性查找 |
| binary_search | O(log n) | 必须已排序 |
| sort | O(n log n) | 快速排序变种 |
| stable_sort | O(n log n) | 归并排序 |
| partial_sort | O(n log k) | k是要排序的元素数 |
| nth_element | O(n) | 选择算法 |
| merge | O(n + m) | 合并两个已排序范围 |
| make_heap | O(n) | 建堆操作 |
以下是对100,000个随机整数进行不同操作的耗时比较(单位:ms):
| 操作 | 耗时(ms) |
|---|---|
| sort | 15 |
| stable_sort | 20 |
| partial_sort(前10%) | 8 |
| nth_element(中位数) | 5 |
| make_heap | 4 |
| sort_heap | 12 |
测试环境:Intel i7-9700K, GCC 10.2, -O3优化
sort,除非需要稳定性partial_sort或nth_element优先使用算法而非手写循环:
理解算法要求:
合理选择算法:
利用现代C++特性:
注意异常安全:
编写清晰的lambda:
配合适当的容器:
性能关键处实测:
书籍推荐:
在线资源:
工具推荐:
进阶主题:
在实际项目中使用STL算法多年,总结几点深刻体会:
算法组合威力大:
通过合理组合算法,可以解决复杂问题。例如,使用transform+accumulate计算加权和,或者remove_if+erase删除特定元素。
lambda是利器:
C++11引入的lambda表达式让算法使用更加灵活。我习惯为复杂lambda添加注释说明其逻辑。
性能不是绝对的:
有时简单的循环可能比复杂算法更高效,特别是在数据量很小的情况下。不要过度追求"算法化"。
注意异常安全:
算法中的谓词和操作函数应该尽量不抛出异常,否则可能导致容器处于中间状态。
学习源码有帮助:
了解STL算法的实现原理(如sort的introsort实现)有助于更好地使用它们。
新特性值得关注:
C++17的并行算法和C++20的范围库大大提升了开发效率和性能,值得投入时间学习。
命名很重要:
为算法操作的结果变量取有意义的名称,如newEnd而不是简单的it,可以提高代码可读性。
测试边界条件:
空范围、单元素范围、所有元素相同等边界情况要特别注意测试。