STL(Standard Template Library)是C++标准库的核心组成部分,提供了丰富的数据结构和算法。其中算法部分尤为强大,涵盖了从简单查找、排序到复杂数值计算的各种功能。STL算法的一个显著特点是它们都是通用的,不依赖于特定的容器类型,而是通过迭代器与容器交互。
STL算法大致可以分为以下几类:
这些算法大多定义在
查找算法是STL中最常用的算法之一,主要包括find、find_if和find_end等。
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_if特别有用,因为它允许我们使用自定义条件进行查找。在实际项目中,我们经常需要根据对象的某个属性来查找,这时find_if就派上用场了。
提示:对于已排序的容器,使用binary_search等算法效率更高,可以达到O(log n)的时间复杂度。
count和count_if用于统计满足条件的元素数量:
cpp复制std::vector<int> vec = {1, 2, 3, 2, 4, 2};
int cnt = std::count(vec.begin(), vec.end(), 2); // 计数2的个数,结果为3
int even_cnt = std::count_if(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0;
}); // 偶数个数,结果为4
count_if的谓词可以是任何返回bool的可调用对象,这使得它非常灵活。在实际应用中,我们经常需要统计满足特定业务条件的对象数量。
for_each算法对范围内的每个元素应用一个函数:
cpp复制std::vector<int> vec = {1, 2, 3, 4, 5};
std::for_each(vec.begin(), vec.end(), [](int& x) {
x *= 2; // 将每个元素乘以2
});
// 现在vec变为{2, 4, 6, 8, 10}
for_each是C++11之前的主要遍历方式,现在有了范围for循环,for_each的使用有所减少,但在某些需要复杂操作的场景下仍然很有用。
equal和mismatch用于比较两个序列:
cpp复制vector<int> a = {1, 2, 3};
vector<int> b = {1, 2, 4};
vector<int> c = {1, 2, 3, 4};
// 比较a和b的前3个元素
bool is_equal = equal(a.begin(), a.end(), b.begin());
cout << "a == b? " << boolalpha << is_equal << endl; // 输出:false
// 查找a和c的第一个不匹配元素
auto mis = mismatch(a.begin(), a.end(), c.begin());
if (mis.first != a.end()) {
cout << "mismatch: " << *mis.first << " vs " << *mis.second << endl; // 无输出(a和c前3元素相等)
}
这些算法在单元测试中特别有用,可以用来验证输出是否符合预期。
all_of、any_of和none_of用于检查范围内的元素是否满足特定条件:
cpp复制std::vector<int> vec = {2, 4, 6, 8};
bool all_even = std::all_of(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0;
}); // true
bool any_odd = std::any_of(vec.begin(), vec.end(), [](int x) {
return x % 2 != 0;
}); // false
bool none_negative = std::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是一个非常有用的迭代器适配器,它会自动调用容器的push_back方法,避免了预先分配空间的麻烦。
transform用于对元素进行转换:
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(), [](int x, int y) {
return x + y;
}); // sum: [5,7,9]
transform是函数式编程风格的典型代表,它鼓励我们将操作抽象为函数对象或lambda表达式。
replace系列算法用于替换元素:
cpp复制vector<int> nums = {1, 2, 3, 2, 5};
// 替换所有2为20
replace(nums.begin(), nums.end(), 2, 20); // nums: [1,20,3,20,5]
// 替换大于10的元素为0
replace_if(nums.begin(), nums.end(), [](int x) {
return x > 10;
}, 0); // nums: [1,0,3,0,5]
// 复制时替换3为300(原容器不变)
vector<int> res;
replace_copy(nums.begin(), nums.end(), back_inserter(res), 3, 300); // res: [1,0,300,0,5]
replace_if特别有用,因为它允许我们根据任意条件进行替换,而不仅仅是简单的值匹配。
remove系列算法用于"删除"元素:
cpp复制vector<int> nums = {1, 2, 3, 2, 4};
// 逻辑删除所有2(移动到末尾)
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]
需要注意的是,remove算法实际上并不删除元素,它只是将要保留的元素移动到前面,并返回新的逻辑结尾。要真正删除元素,需要结合容器的erase方法。
unique用于移除连续的重复元素:
cpp复制std::vector<int> vec = {1, 1, 2, 2, 3, 3, 3, 4, 5};
auto last = std::unique(vec.begin(), vec.end());
vec.erase(last, vec.end()); // vec变为{1, 2, 3, 4, 5}
reverse用于反转元素顺序:
cpp复制std::vector<int> vec = {1, 2, 3, 4, 5};
std::reverse(vec.begin(), vec.end()); // vec变为{5, 4, 3, 2, 1}
rotate用于旋转元素:
cpp复制std::vector<int> vec = {1, 2, 3, 4, 5};
std::rotate(vec.begin(), vec.begin() + 2, vec.end()); // 以3为起点旋转,vec变为{3, 4, 5, 1, 2}
shuffle用于随机打乱元素:
cpp复制#include <random>
#include <algorithm>
std::vector<int> vec = {1, 2, 3, 4, 5};
std::random_device rd;
std::mt19937 g(rd());
std::shuffle(vec.begin(), vec.end(), g); // 随机打乱vec中的元素
这些算法各有用途,掌握它们可以大大提高编码效率。
sort是STL中最常用的算法之一:
cpp复制std::vector<int> vec = {5, 3, 1, 4, 2};
std::sort(vec.begin(), vec.end()); // 默认升序,vec变为{1, 2, 3, 4, 5}
std::sort(vec.begin(), vec.end(), std::greater<int>()); // 降序,vec变为{5, 4, 3, 2, 1}
对于需要保持相等元素相对顺序的情况,可以使用stable_sort:
cpp复制std::vector<std::pair<int, int>> vec = {{1, 2}, {2, 1}, {1, 1}, {2, 2}};
std::stable_sort(vec.begin(), vec.end(), [](const auto& a, const auto& b) {
return a.first < b.first; // 按first排序,保持相等元素的相对顺序
});
partial_sort用于部分排序:
cpp复制std::vector<int> vec = {5, 3, 1, 4, 2, 6};
// 将最小的3个元素放在前面并排序
std::partial_sort(vec.begin(), vec.begin() + 3, vec.end());
// 现在vec前三个元素是1, 2, 3,后面是未排序的4, 5, 6
nth_element用于找到第n小的元素:
cpp复制std::vector<int> vec = {5, 3, 1, 4, 2, 6};
// 找到第三小的元素(索引2)
std::nth_element(vec.begin(), vec.begin() + 2, vec.end());
// 现在vec[2]是3,它左边的元素<=3,右边的>=3
这个算法在需要找中位数或前k个元素时特别有用,它的时间复杂度是O(n),比完全排序更高效。
二分查找算法要求容器必须是已排序的:
cpp复制vector<int> sorted = {1, 3, 3, 5, 7}; // 必须先排序
// 判断3是否存在
bool exists = binary_search(sorted.begin(), sorted.end(), 3); // true
// 查找第一个>=3的元素
auto lb = lower_bound(sorted.begin(), sorted.end(), 3);
cout << "lower_bound index: " << lb - sorted.begin() << endl; // 输出:1
// 查找第一个>3的元素
auto ub = upper_bound(sorted.begin(), sorted.end(), 3);
cout << "upper_bound index: " << ub - sorted.begin() << endl; // 输出:3
lower_bound和upper_bound在实现范围查询时非常有用,比如在时间序列数据中查找特定时间范围内的数据。
merge用于合并两个已排序的序列:
cpp复制vector<int> a = {1, 3, 5};
vector<int> b = {2, 4, 6};
vector<int> merged(a.size() + b.size());
// 合并a和b(均需已排序)
merge(a.begin(), a.end(), b.begin(), b.end(), merged.begin()); // merged: [1,2,3,4,5,6]
merge算法是归并排序的核心,也可以用于实现类似数据库的merge join操作。
STL提供了一组堆算法,可以将任何随机访问迭代器范围作为堆来操作:
cpp复制std::vector<int> vec = {4, 1, 3, 2, 5};
std::make_heap(vec.begin(), vec.end()); // 构建最大堆,vec变为{5, 4, 3, 2, 1}
vec.push_back(6);
std::push_heap(vec.begin(), vec.end()); // 将新元素加入堆,vec变为{6, 4, 5, 2, 1, 3}
std::pop_heap(vec.begin(), vec.end()); // 将最大元素移到末尾,vec变为{5, 4, 3, 2, 1, 6}
int max_val = vec.back(); // 获取最大元素6
vec.pop_back(); // 移除最大元素
std::sort_heap(vec.begin(), vec.end()); // 将堆排序为升序序列,vec变为{1, 2, 3, 4, 5}
堆算法通常用于实现优先级队列,在需要频繁获取最大或最小元素的场景下非常高效。
min和max用于比较两个值或初始化列表:
cpp复制int a = 5, b = 3;
int min_val = std::min(a, b); // 3
int max_val = std::max(a, b); // 5
auto min_of_list = std::min({4, 2, 8, 5, 1}); // 1
auto max_of_list = std::max({4, 2, 8, 5, 1}); // 8
min_element和max_element用于查找范围内的极值:
cpp复制std::vector<int> vec = {3, 1, 4, 2, 5};
auto min_it = std::min_element(vec.begin(), vec.end()); // 指向1
auto max_it = std::max_element(vec.begin(), vec.end()); // 指向5
minmax_element可以同时找到最小和最大值:
cpp复制std::vector<int> vec = {3, 1, 4, 2, 5};
auto minmax = std::minmax_element(vec.begin(), vec.end());
// minmax.first指向1,minmax.second指向5
这些算法在统计分析、数据预处理等场景下非常有用。
数值算法定义在
accumulate用于计算累加和或自定义操作:
cpp复制#include <numeric>
std::vector<int> vec = {1, 2, 3, 4, 5};
int sum = std::accumulate(vec.begin(), vec.end(), 0); // 和,初始值为0,结果为15
int product = std::accumulate(vec.begin(), vec.end(), 1, std::multiplies<int>()); // 乘积,初始值为1,结果为120
accumulate非常灵活,可以用于实现各种归约操作。
inner_product用于计算两个序列的内积:
cpp复制std::vector<int> a = {1, 2, 3};
std::vector<int> b = {4, 5, 6};
int dot = std::inner_product(a.begin(), a.end(), b.begin(), 0); // 1*4 + 2*5 + 3*6 = 32
除了标准的点积,inner_product还可以通过提供自定义操作来实现其他类型的归约。
iota用于填充连续递增的值:
cpp复制std::vector<int> vec(5);
std::iota(vec.begin(), vec.end(), 10); // 填充为10, 11, 12, 13, 14
这个算法在需要生成连续序列时非常方便。
partial_sum用于计算部分和:
cpp复制std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst(src.size());
std::partial_sum(src.begin(), src.end(), dst.begin()); // dst变为{1, 3, 6, 10, 15}
部分和在金融计算、统计分析等领域有广泛应用。
adjacent_difference用于计算相邻元素的差:
cpp复制std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst(src.size());
std::adjacent_difference(src.begin(), src.end(), dst.begin()); // dst变为{1, 1, 1, 1, 1}
这个算法可以用于计算时间序列数据的变化率等。
generate和generate_n用于用生成函数填充容器:
cpp复制std::vector<int> vec(5);
int n = 0;
std::generate(vec.begin(), vec.end(), [&n]() {
return n++;
}); // 填充为0, 1, 2, 3, 4
std::generate_n(vec.begin(), 3, [&n]() {
return n++;
}); // 前三个元素为10, 11, 12,后两个保持不变
这些算法在需要生成测试数据时特别有用。
includes用于检查一个排序范围是否包含另一个排序范围的所有元素:
cpp复制std::vector<int> vec1 = {1, 2, 3, 4, 5};
std::vector<int> vec2 = {2, 4};
bool includes = std::includes(vec1.begin(), vec1.end(), vec2.begin(), vec2.end()); // true
STL提供了一组集合算法,包括并集、交集、差集和对称差集:
cpp复制std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<int> v2 = {3, 4, 5, 6, 7};
std::vector<int> result;
// 并集
std::set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(result));
// result为{1, 2, 3, 4, 5, 6, 7}
// 交集
result.clear();
std::set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(result));
// result为{3, 4, 5}
// 差集 (v1 - v2)
result.clear();
std::set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(result));
// result为{1, 2}
// 对称差集 (v1 ∪ v2 - v1 ∩ v2)
result.clear();
std::set_symmetric_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(result));
// result为{1, 2, 6, 7}
这些集合算法在数据处理和分析中非常有用,可以替代很多手写的循环代码。
选择依据:
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,2]
nums.erase(new_end, nums.end()); // nums: [1,3,4]
这种模式被称为"erase-remove惯用法",是STL中常见的模式。
以下算法要求容器必须是已排序的:
在这些算法中使用未排序的容器会导致未定义行为或错误结果。
不同算法的时间复杂度差异很大:
选择算法时,应该根据操作频率和数据规模选择最合适的算法。
许多算法允许传入自定义比较函数,这使得算法更加灵活:
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;
});
自定义比较函数应该实现严格的弱序关系,否则可能导致未定义行为。
C++17引入了并行算法,许多STL算法现在都有并行版本:
cpp复制#include <execution>
vector<int> v = {...};
// 并行排序
sort(std::execution::par, v.begin(), v.end());
// 并行查找
auto it = find(std::execution::par, v.begin(), v.end(), 42);
并行算法可以充分利用多核处理器的计算能力,对于大规模数据可以显著提高性能。