作为C++开发者,标准库算法是我们日常工作中不可或缺的利器。这些算法封装了常见的数据操作模式,不仅提高了代码的可读性,还能通过编译器的优化获得更好的性能。根据对容器元素的影响程度,标准库算法主要分为两大类:
提示:使用算法时务必注意迭代器有效性和容器预分配空间的问题,特别是在修改操作中。
查找算法是日常开发中使用频率最高的一类算法,STL提供了多种查找方式:
cpp复制// 基本查找示例
vector<int> data = {10, 20, 30, 40, 50};
// 查找等于30的元素
auto it = find(data.begin(), data.end(), 30);
if (it != data.end()) {
cout << "Found at position: " << distance(data.begin(), it) << endl;
}
// 使用谓词查找第一个大于35的元素
auto it2 = find_if(data.begin(), data.end(), [](int x) {
return x > 35;
});
性能考虑:
find和find_if是线性查找,时间复杂度O(n)binary_search等二分查找算法(O(log n))统计和验证是数据处理中的常见需求:
cpp复制vector<int> scores = {85, 92, 78, 90, 85, 87, 85};
// 统计特定值出现次数
int count85 = count(scores.begin(), scores.end(), 85);
// 统计满足条件的元素数量
int excellent = count_if(scores.begin(), scores.end(), [](int s) {
return s >= 90;
});
// 检查所有元素是否满足条件
bool allPass = all_of(scores.begin(), scores.end(), [](int s) {
return s >= 60;
});
比较两个序列或对每个元素执行操作:
cpp复制vector<int> a = {1, 2, 3};
vector<int> b = {1, 2, 4};
// 序列相等性比较
bool same = equal(a.begin(), a.end(), b.begin());
// 查找第一个不匹配的位置
auto mismatch_pair = mismatch(a.begin(), a.end(), b.begin());
// 对每个元素执行操作
for_each(a.begin(), a.end(), [](int& x) {
x *= 2;
});
复制和转换是数据处理的基础操作:
cpp复制vector<int> source = {1, 2, 3, 4, 5};
vector<int> target(source.size());
// 基本复制
copy(source.begin(), source.end(), target.begin());
// 条件复制
vector<int> evens;
copy_if(source.begin(), source.end(), back_inserter(evens), [](int x) {
return x % 2 == 0;
});
// 元素变换
vector<int> squares;
transform(source.begin(), source.end(), back_inserter(squares), [](int x) {
return x * x;
});
注意:使用
back_inserter时不需要预先分配空间,但频繁扩容可能影响性能。对于已知大小的操作,建议预先分配足够空间。
安全地修改容器内容:
cpp复制vector<int> numbers = {1, 2, 3, 2, 5, 2};
// 简单替换
replace(numbers.begin(), numbers.end(), 2, 20);
// 条件替换
replace_if(numbers.begin(), numbers.end(), [](int x) {
return x > 10;
}, 0);
// 删除元素(remove+erase惯用法)
numbers.erase(remove(numbers.begin(), numbers.end(), 0), numbers.end());
关键点:
remove算法只是将待删除元素移到末尾,返回新的逻辑终点erase才能真正减少容器大小高效的排序和元素重排:
cpp复制vector<int> data = {5, 3, 2, 4, 1};
// 基本排序
sort(data.begin(), data.end());
// 自定义排序
sort(data.begin(), data.end(), greater<int>());
// 部分排序
partial_sort(data.begin(), data.begin() + 3, data.end());
// 随机重排
random_device rd;
mt19937 g(rd());
shuffle(data.begin(), data.end(), g);
性能对比:
| 算法 | 时间复杂度 | 稳定性 | 适用场景 |
|---|---|---|---|
| sort | O(n log n) | 不稳定 | 通用排序 |
| stable_sort | O(n log n) | 稳定 | 需要保持相等元素顺序 |
| partial_sort | O(n log k) | 不稳定 | 只关心前k个元素 |
堆算法常用于优先级队列的实现:
cpp复制vector<int> nums = {3, 1, 4, 1, 5, 9};
// 构建最大堆
make_heap(nums.begin(), nums.end());
// 添加新元素
nums.push_back(6);
push_heap(nums.begin(), nums.end());
// 取出最大元素
pop_heap(nums.begin(), nums.end());
int max = nums.back();
nums.pop_back();
cpp复制vector<int> v = {1, 2, 3, 4, 5};
// 累加求和
int sum = accumulate(v.begin(), v.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> prefix(v.size());
partial_sum(v.begin(), v.end(), prefix.begin());
处理已排序范围的集合运算:
cpp复制vector<int> set1 = {1, 2, 3, 4, 5};
vector<int> set2 = {3, 4, 5, 6, 7};
vector<int> result;
// 并集
set_union(set1.begin(), set1.end(),
set2.begin(), set2.end(),
back_inserter(result));
// 交集
result.clear();
set_intersection(set1.begin(), set1.end(),
set2.begin(), set2.end(),
back_inserter(result));
stable_sort需要额外内存,内存紧张时慎用back_inserter、front_inserter、inserter简化容器扩展reverse_iterator等扩展算法应用场景const auto&cpp复制// 良好的lambda实践
vector<Person> people;
sort(people.begin(), people.end(), [](const auto& a, const auto& b) {
return a.age < b.age;
});
要使自定义类型支持标准算法,通常需要:
hash函数(用于无序容器)cpp复制struct Point {
int x, y;
bool operator<(const Point& other) const {
return x < other.x || (x == other.x && y < other.y);
}
};
vector<Point> points = {{1,2}, {3,4}, {1,1}};
sort(points.begin(), points.end());
通过组合算法实现复杂操作:
cpp复制// 删除所有满足条件的元素
vector<int> data = {...};
data.erase(remove_if(data.begin(), data.end(), [](int x) {
return x % 2 == 0;
}), data.end());
// 去重后排序
sort(data.begin(), data.end());
data.erase(unique(data.begin(), data.end()), data.end());
利用并行执行策略提升性能:
cpp复制#include <execution>
vector<int> bigData(1000000);
// 并行排序
sort(execution::par, bigData.begin(), bigData.end());
// 并行变换
transform(execution::par,
bigData.begin(), bigData.end(),
bigData.begin(), [](int x) {
return x * 2;
});
注意:并行算法可能引入线程安全问题,确保操作是无副作用的
cpp复制vector<SaleRecord> sales = LoadSalesData();
// 过滤无效记录
sales.erase(remove_if(sales.begin(), sales.end(), [](const auto& r) {
return r.amount <= 0;
}), sales.end());
// 按销售额降序排列
sort(sales.begin(), sales.end(), [](const auto& a, const auto& b) {
return a.amount > b.amount;
});
// 计算总销售额
double total = accumulate(sales.begin(), sales.end(), 0.0, [](double sum, const auto& r) {
return sum + r.amount;
});
// 找出前10%的高价值交易
size_t topCount = sales.size() / 10;
partial_sort(sales.begin(), sales.begin() + topCount, sales.end(),
[](const auto& a, const auto& b) {
return a.amount > b.amount;
});
cpp复制vector<Enemy> enemies = GetActiveEnemies();
// 按距离排序(最近的优先处理)
sort(enemies.begin(), enemies.end(), [playerPos](const auto& a, const auto& b) {
return Distance(a.position, playerPos) < Distance(b.position, playerPos);
});
// 找出视野内的敌人
vector<Enemy*> visibleEnemies;
copy_if(enemies.begin(), enemies.end(), back_inserter(visibleEnemies), [playerPos](const auto& e) {
return IsVisible(e.position, playerPos);
});
// 对可见敌人造成伤害
for_each(visibleEnemies.begin(), visibleEnemies.end(), [](auto& e) {
e.health -= CalculateDamage(e);
});
// 移除死亡敌人
enemies.erase(remove_if(enemies.begin(), enemies.end(), [](const auto& e) {
return e.health <= 0;
}), enemies.end());
cpp复制void TestAlgorithmPerformance() {
const size_t size = 1000000;
vector<int> data(size);
iota(data.begin(), data.end(), 0);
shuffle(data.begin(), data.end(), default_random_engine{});
auto testSort = [&](auto algorithm, const string& name) {
auto copy = data;
auto start = chrono::high_resolution_clock::now();
algorithm(copy.begin(), copy.end());
auto end = chrono::high_resolution_clock::now();
cout << name << ": "
<< chrono::duration_cast<chrono::milliseconds>(end-start).count()
<< " ms" << endl;
};
testSort(sort, "sort");
testSort(stable_sort, "stable_sort");
testSort([](auto b, auto e) {
partial_sort(b, e, e);
}, "partial_sort (full)");
}
理解标准算法实现原理有助于更好地使用它们:
cpp复制template<typename InputIt, typename UnaryPredicate>
InputIt my_find_if(InputIt first, InputIt last, UnaryPredicate p) {
for (; first != last; ++first) {
if (p(*first)) {
return first;
}
}
return last;
}
template<typename ForwardIt, typename T>
void my_replace(ForwardIt first, ForwardIt last, const T& old_val, const T& new_val) {
for (; first != last; ++first) {
if (*first == old_val) {
*first = new_val;
}
}
}
理解常见算法的时间复杂度:
| 算法 | 平均复杂度 | 最坏复杂度 | 空间复杂度 |
|---|---|---|---|
| find | O(n) | O(n) | O(1) |
| sort | O(n log n) | O(n log n) | O(log n) |
| nth_element | O(n) | O(n²) | O(1) |
| binary_search | O(log n) | O(log n) | O(1) |
| set_union | O(n+m) | O(n+m) | O(1) |
C++20引入了一些新算法:
cpp复制// 判断范围是否已排序
vector<int> v = {1, 2, 3};
bool sorted = is_sorted_until(v.begin(), v.end()) == v.end();
// 范围比较
vector<int> a = {1, 2, 3};
vector<int> b = {1, 2, 4};
auto [a_it, b_it] = mismatch(a.begin(), a.end(), b.begin(), b.end());
// 投影(Projection)支持
vector<Person> people = {{"Alice",25}, {"Bob",30}};
sort(people.begin(), people.end(), {}, &Person::age); // 按年龄排序
在实际项目中,合理选择和使用标准算法可以显著提高代码质量和性能。我个人的经验是,当发现自己在写循环处理容器元素时,先考虑是否能用现有算法替代,这往往能写出更简洁、更高效的代码。