作为C++开发者,我们每天都在与各种数据结构和算法打交道。STL(Standard Template Library)提供了一套强大而高效的算法库,可以极大地提升我们的开发效率。这些算法主要定义在<algorithm>和<numeric>头文件中,涵盖了从简单的查找、排序到复杂的数值计算等各种场景。
STL算法的一个显著特点是它们都是通过迭代器来操作容器,这意味着它们可以与任何支持迭代器的数据结构一起工作,包括数组、vector、list等。这种设计体现了泛型编程的思想,使得算法与数据结构解耦,提高了代码的复用性。
查找算法是日常开发中最常用的算法之一。STL提供了多种查找方式:
cpp复制vector<int> nums = {1, 3, 5, 7, 9};
// 基本查找:find
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;
});
// 子序列查找:find_end
vector<int> sub = {3, 5};
auto it3 = find_end(nums.begin(), nums.end(), sub.begin(), sub.end());
注意:对于已排序的序列,应优先使用binary_search等二分查找算法,它们的时间复杂度是O(log n),比线性查找的O(n)更高效。
计数算法用于统计满足特定条件的元素数量:
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
for_each算法提供了一种简洁的遍历方式:
cpp复制vector<int> vec = {1, 2, 3, 4, 5};
for_each(vec.begin(), vec.end(), [](int& x) {
x *= 2;
});
// vec变为{2, 4, 6, 8, 10}
equal和mismatch算法用于比较两个序列:
cpp复制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;
}
all_of、any_of和none_of用于检查序列中元素是否满足特定条件:
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());
// 条件复制
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;
});
// 双序列变换
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;
});
替换算法可以修改序列中的特定元素:
cpp复制vector<int> nums = {1, 2, 3, 2, 5};
// 基本替换
replace(nums.begin(), nums.end(), 2, 20);
// 条件替换
replace_if(nums.begin(), nums.end(), [](int x) {
return x > 10;
}, 0);
// 复制时替换
vector<int> res;
replace_copy(nums.begin(), nums.end(), back_inserter(res), 3, 300);
删除算法需要特别注意,因为remove系列算法实际上并不删除元素:
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.erase(remove_if(nums.begin(), nums.end(), [](int x) {
return x % 2 == 0;
}), nums.end());
unique算法用于去除连续重复元素:
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和rotate算法用于改变元素顺序:
cpp复制vector<int> vec = {1, 2, 3, 4, 5};
reverse(vec.begin(), vec.end());
// vec变为{5, 4, 3, 2, 1}
rotate(vec.begin(), vec.begin() + 2, vec.end());
// vec变为{3, 4, 5, 1, 2}
shuffle算法用于随机打乱序列:
cpp复制vector<int> vec = {1, 2, 3, 4, 5};
random_device rd;
mt19937 g(rd());
shuffle(vec.begin(), vec.end(), g);
sort是使用最广泛的排序算法:
cpp复制vector<int> vec = {5, 3, 1, 4, 2};
// 默认升序
sort(vec.begin(), vec.end());
// 降序
sort(vec.begin(), vec.end(), greater<int>());
// 自定义比较
sort(vec.begin(), vec.end(), [](int a, int b) {
return a < b;
});
stable_sort保证相等元素的相对顺序不变:
cpp复制vector<pair<int, int>> vec = {{1, 2}, {2, 1}, {1, 1}, {2, 2}};
stable_sort(vec.begin(), vec.end(), [](const auto& a, const auto& b) {
return a.first < b.first;
});
partial_sort用于部分排序:
cpp复制vector<int> vec = {5, 3, 1, 4, 2, 6};
partial_sort(vec.begin(), vec.begin() + 3, vec.end());
// 前三个元素是1, 2, 3,后面未排序
nth_element用于快速选择:
cpp复制vector<int> vec = {5, 3, 1, 4, 2, 6};
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);
auto lb = lower_bound(sorted.begin(), sorted.end(), 3);
// 第一个不小于3的元素
auto ub = upper_bound(sorted.begin(), sorted.end(), 3);
// 第一个大于3的元素
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}
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();
vec.pop_back();
// 堆排序
sort_heap(vec.begin(), vec.end());
数值算法定义在
cpp复制vector<int> vec = {1, 2, 3, 4, 5};
// 累加
int sum = accumulate(vec.begin(), vec.end(), 0);
// 内积
vector<int> a = {1, 2, 3};
vector<int> b = {4, 5, 6};
int dot = inner_product(a.begin(), a.end(), b.begin(), 0);
// 填充递增序列
vector<int> seq(5);
iota(seq.begin(), seq.end(), 10);
// seq: {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());
// dst: {1, 3, 6, 10, 15}
// 相邻差值
adjacent_difference(src.begin(), src.end(), dst.begin());
// dst: {1, 1, 1, 1, 1}
generate和generate_n用于填充序列:
cpp复制vector<int> vec(5);
int n = 0;
generate(vec.begin(), vec.end(), [&n]() {
return n++;
});
// vec: {0, 1, 2, 3, 4}
generate_n(vec.begin(), 3, [&n]() {
return n++;
});
// 前三个元素被修改
集合算法要求输入序列已排序:
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.clear();
set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), back_inserter(result));
// 差集
result.clear();
set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), back_inserter(result));
// 对称差集
result.clear();
set_symmetric_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), back_inserter(result));
在实际开发中,选择合适的算法需要考虑多个因素:
时间复杂度:了解算法的时间复杂度对于处理大数据集至关重要。例如,线性查找O(n)和二分查找O(log n)的性能差异会随着数据量增大而显著。
稳定性:对于需要保持相等元素相对顺序的场景,应选择stable_sort等稳定算法。
内存使用:某些算法如stable_sort可能需要额外的内存空间。
数据特性:对于部分有序的数据,可以考虑使用partial_sort或nth_element等算法。
迭代器类别:不同算法对迭代器的要求不同,例如随机访问迭代器或前向迭代器。
cpp复制string text = "hello world hello cpp world algorithm";
istringstream iss(text);
vector<string> words(istream_iterator<string>{iss}, {});
sort(words.begin(), words.end());
vector<pair<string, int>> word_counts;
auto it = words.begin();
while (it != words.end()) {
auto range = equal_range(it, words.end(), *it);
word_counts.emplace_back(*it, distance(range.first, range.second));
it = range.second;
}
// 按频率降序排序
sort(word_counts.begin(), word_counts.end(), [](const auto& a, const auto& b) {
return a.second > b.second;
});
cpp复制vector<int> set1 = {1, 3, 5, 7, 9};
vector<int> set2 = {2, 3, 5, 7, 11};
// 确保已排序
sort(set1.begin(), set1.end());
sort(set2.begin(), set2.end());
vector<int> intersection;
set_intersection(set1.begin(), set1.end(),
set2.begin(), set2.end(),
back_inserter(intersection));
// intersection: {3, 5, 7}
cpp复制struct Person {
string name;
int age;
double salary;
};
vector<Person> people = {
{"Alice", 30, 50000},
{"Bob", 25, 45000},
{"Charlie", 35, 60000}
};
// 按年龄升序,薪水降序
sort(people.begin(), people.end(), [](const Person& a, const Person& b) {
if (a.age != b.age) return a.age < b.age;
return a.salary > b.salary;
});
remove算法实际上并不删除元素,而是将要保留的元素移动到序列前面,返回新的逻辑结尾迭代器。要真正删除元素,需要结合erase:
cpp复制vector<int> vec = {1, 2, 3, 2, 4};
auto new_end = remove(vec.begin(), vec.end(), 2);
vec.erase(new_end, vec.end());
这种设计是为了提高效率,避免频繁的内存重新分配。
现代C++中,lambda表达式极大地增强了算法的表达能力:
cpp复制vector<Employee> employees = {...};
// 查找特定条件的员工
auto it = find_if(employees.begin(), employees.end(), [](const Employee& e) {
return e.age > 30 && e.department == "IT";
});
// 自定义排序
sort(employees.begin(), employees.end(), [](const Employee& a, const Employee& b) {
return tie(a.department, a.salary) < tie(b.department, b.salary);
});
lambda使得我们可以轻松地定义各种复杂的条件和操作,大大提高了算法的灵活性。
C++11/14/17/20为算法库带来了许多改进:
C++17引入了并行执行策略:
cpp复制#include <execution>
vector<int> big_data(1000000);
// 并行排序
sort(execution::par, big_data.begin(), big_data.end());
// 并行变换
transform(execution::par,
big_data.begin(), big_data.end(),
big_data.begin(), [](int x) {
return x * x;
});
可用的执行策略包括:
C++17新增了一些实用算法:
cpp复制// 样本采样
vector<int> out;
sample(data.begin(), data.end(), back_inserter(out), 10, mt19937{random_device{}()});
// 并行查找
auto result = find(execution::par, data.begin(), data.end(), 42);
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; });
// 排序并去重
sort(vec);
auto unique = vec | views::unique;
范围库使得算法的组合和链式调用更加直观和高效。
了解不同算法的性能特点对于实际应用至关重要。下面是一些常见算法的性能比较:
排序算法:
查找算法:
集合操作:
在实际应用中,应根据数据规模、是否已排序、是否需要稳定性等因素选择合适的算法。
虽然STL算法强大,但过度使用可能会降低代码可读性。建议:
算法通常不直接提供错误处理机制,需要注意:
虽然STL提供了丰富的算法,但有时我们需要自定义算法:
cpp复制template <typename InputIt, typename OutputIt, typename Predicate>
OutputIt copy_if_not(InputIt first, InputIt last, OutputIt d_first, Predicate pred) {
return copy_if(first, last, d_first, [&pred](const auto& x) {
return !pred(x);
});
}
将多个算法组合起来实现复杂功能:
cpp复制// 删除所有满足条件的元素
template <typename Container, typename Predicate>
void erase_if(Container& c, Predicate pred) {
c.erase(remove_if(c.begin(), c.end(), pred), c.end());
}
针对特定场景优化算法:
cpp复制// 优化的小型数组排序
template <typename RandomIt>
void small_sort(RandomIt first, RandomIt last) {
if (distance(first, last) <= 32) {
insertion_sort(first, last);
} else {
sort(first, last);
}
}
STL算法是C++编程中不可或缺的工具,掌握它们可以显著提高代码质量和开发效率。本文涵盖了大多数常用算法,但STL的功能远不止于此。要进一步提升:
记住,选择正确的算法往往比微优化更重要。理解问题本质和数据特性,才能选择最适合的算法解决方案。