C++标准库提供了丰富的算法,主要定义在<algorithm>和<numeric>头文件中。这些算法可以大大简化我们的日常编程工作,避免重复造轮子。根据算法对容器的影响,我们可以将其分为两大类:非修改序列算法和修改序列算法。
非修改序列算法不会改变容器中的元素内容,它们主要用于查询和检查容器中的数据。这类算法包括查找、计数、遍历等操作。
cpp复制#include <algorithm>
#include <vector>
using namespace std;
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
}
修改序列算法会直接改变容器中的元素内容,包括排序、替换、删除、变换等操作。使用这些算法时需要特别注意迭代器失效问题。
cpp复制vector<int> nums = {1, 2, 3, 4, 5};
// 将每个元素乘以2
transform(nums.begin(), nums.end(), nums.begin(), [](int x) {
return x * 2;
});
// nums现在为{2, 4, 6, 8, 10}
find用于查找特定值,find_if则可以根据谓词条件查找元素。
cpp复制vector<int> vec = {1, 2, 3, 4, 5};
// 查找值为3的元素
auto it = find(vec.begin(), vec.end(), 3);
// 查找第一个大于3的元素
auto it2 = find_if(vec.begin(), vec.end(), [](int x) {
return x > 3;
});
注意:如果未找到元素,这些算法会返回end()迭代器,使用前务必检查返回值。
find_end查找子序列最后一次出现的位置,search查找子序列第一次出现的位置。
cpp复制vector<int> main = {1, 2, 3, 4, 1, 2, 3};
vector<int> sub = {1, 2, 3};
// 查找最后一次出现的位置
auto last = find_end(main.begin(), main.end(), sub.begin(), sub.end());
// 查找第一次出现的位置
auto first = search(main.begin(), main.end(), sub.begin(), sub.end());
count统计特定值出现的次数,count_if统计满足条件的元素数量。
cpp复制vector<int> vec = {1, 2, 2, 3, 2, 4, 2};
int cnt = count(vec.begin(), vec.end(), 2); // 结果为4
int even_cnt = count_if(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0;
}); // 偶数个数,结果为5
for_each对范围内的每个元素应用一个函数,是替代传统for循环的更安全方式。
cpp复制vector<int> vec = {1, 2, 3, 4, 5};
for_each(vec.begin(), vec.end(), [](int& x) {
x *= 2; // 将每个元素乘以2
});
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 mis = mismatch(a.begin(), a.end(), b.begin());
// mis.first指向3,mis.second指向4
这些算法检查范围内元素是否全部/存在/没有满足特定条件。
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
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可以自动处理目标容器空间不足的问题。
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}
replace替换特定值,replace_if替换满足条件的元素。
cpp复制vector<int> nums = {1, 2, 3, 2, 5};
replace(nums.begin(), nums.end(), 2, 20); // 替换所有2为20
replace_if(nums.begin(), nums.end(), [](int x) {
return x > 10;
}, 0); // 替换大于10的元素为0
remove和remove_if实际上并不删除元素,而是将不需要的元素移动到容器末尾。
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()); // 真正删除元素
重要:
remove算法必须配合erase使用才能真正删除元素,这是C++中常见的erase-remove惯用法。
unique移除连续的重复元素,通常也需要配合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}
sort是快速排序实现,stable_sort是稳定的归并排序。
cpp复制vector<int> vec = {5, 3, 1, 4, 2};
sort(vec.begin(), vec.end()); // 默认升序
stable_sort(vec.begin(), vec.end(), greater<int>()); // 稳定降序排序
partial_sort对部分元素进行排序,常用于获取前N个最小/最大元素。
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
nth_element重新排列元素,使得第n个位置的元素是正确的。
cpp复制vector<int> vec = {5, 3, 1, 4, 2, 6};
// 找到第三小的元素
nth_element(vec.begin(), vec.begin() + 2, vec.end());
// vec[2]是3,左边<=3,右边>=3
这些算法要求范围已经是排序的。
binary_search检查值是否存在。
cpp复制vector<int> sorted = {1, 3, 3, 5, 7};
bool exists = binary_search(sorted.begin(), sorted.end(), 3); // true
lower_bound返回第一个不小于给定值的迭代器,upper_bound返回第一个大于给定值的迭代器。
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); // 指向5
merge合并两个已排序的范围。
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}
这些算法定义在<numeric>头文件中。
accumulate计算累加和或自定义操作的结果。
cpp复制#include <numeric>
vector<int> vec = {1, 2, 3, 4, 5};
int sum = accumulate(vec.begin(), vec.end(), 0); // 和,结果为15
int product = accumulate(vec.begin(), vec.end(), 1, multiplies<int>()); // 乘积,120
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
iota用连续递增的值填充范围。
cpp复制vector<int> vec(5);
iota(vec.begin(), vec.end(), 10); // 填充为10, 11, 12, 13, 14
partial_sum计算部分和。
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> vec = {1, 2, 3, 4, 5};
auto it = vec.begin() + 2; // 指向3
vec.erase(vec.begin()); // 删除第一个元素
// 此时it可能已经失效,不能再使用
许多算法接受谓词函数,设计良好的谓词可以提高代码的可读性和性能。
cpp复制// 不好的谓词设计
auto pred = [](int x) { return x > 5 && x < 10 && x % 2 == 0; };
// 更好的设计
bool is_even_in_range(int x) {
const int lower = 5;
const int upper = 10;
return x > lower && x < upper && x % 2 == 0;
}
不同算法的时间复杂度不同,应根据实际需求选择合适的算法。
find: O(n)binary_search: O(log n) (但要求范围已排序)sort: O(n log n)nth_element: O(n)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;
});
cpp复制vector<int> data = {1, -1, 2, -2, 3, -3, 4, -4};
// 移除所有负数
data.erase(remove_if(data.begin(), data.end(), [](int x) {
return x < 0;
}), data.end());
cpp复制vector<int> a = {1, 2, 3, 4, 5};
vector<int> b = {3, 4, 5, 6, 7};
vector<int> common;
set_intersection(a.begin(), a.end(), b.begin(), b.end(), back_inserter(common));
// common: {3, 4, 5}
remove算法设计为通用算法,它不知道容器的具体实现,因此无法直接修改容器大小。它只是将不需要的元素移动到末尾,返回新的逻辑结尾。真正的删除需要通过容器的erase方法完成。
如果需要保持相等元素的相对顺序,使用stable_sort。否则,通常sort性能更好,因为它使用的是快速排序的变体(introsort)。
可以通过传递自定义比较函数或函数对象来定义排序规则。
cpp复制vector<string> words = {"apple", "banana", "cherry"};
// 按长度排序
sort(words.begin(), words.end(), [](const string& a, const string& b) {
return a.size() < b.size();
});
所有标准算法都能正确处理空范围(即begin == end)。例如,find会返回end,count会返回0,sort不会有任何效果。