C++常用算法深度解析:从STL到现代C++的算法艺术

目录

一、STL算法分类与哲学

[1.1 算法分类体系](#1.1 算法分类体系)

[1.2 STL算法设计哲学](#1.2 STL算法设计哲学)

二、非修改序列操作算法

[2.1 查找算法实战](#2.1 查找算法实战)

[2.2 计数与比较算法](#2.2 计数与比较算法)

三、修改序列操作算法

[3.1 拷贝与移动算法](#3.1 拷贝与移动算法)

[3.2 变换与替换算法](#3.2 变换与替换算法)

[3.3 删除与去重算法](#3.3 删除与去重算法)

四、排序与相关算法

[4.1 排序算法详解](#4.1 排序算法详解)

[4.2 堆算法](#4.2 堆算法)

[4.3 集合算法](#4.3 集合算法)

五、数值算法

[5.1 累加与内积](#5.1 累加与内积)

[5.2 部分和与相邻差](#5.2 部分和与相邻差)

[六、C++20 Ranges算法(现代C++)](#六、C++20 Ranges算法(现代C++))

[6.1 Ranges视图管道](#6.1 Ranges视图管道)

[6.2 范围适配器组合](#6.2 范围适配器组合)

七、性能优化与最佳实践

[7.1 算法选择指南](#7.1 算法选择指南)

[7.2 避免常见陷阱](#7.2 避免常见陷阱)

[7.3 自定义算法扩展](#7.3 自定义算法扩展)


如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。

一、STL算法分类与哲学

1.1 算法分类体系

非修改序列操作 (25个) ← 只读遍历,不改变容器

↓ find, count, for_each, search, equal, mismatch

修改序列操作 (27个) ← 改变元素值或顺序

↓ copy, move, transform, replace, fill, reverse, rotate

排序与相关操作 (15个) ← 排序、二分查找、集合运算

↓ sort, stable_sort, partial_sort, nth_element, binary_search

数值算法 (5个) ← 数学计算

↓ accumulate, inner_product, partial_sum, adjacent_difference

1.2 STL算法设计哲学

// 通用性:通过迭代器抽象,适用于任何容器

template<typename InputIt, typename UnaryFunction>

UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f) {

for (; first != last; ++first) {

f(*first); // 对每个元素应用函数

}

return f; // 返回函数对象,可携带状态

}

// 组合性:算法可以链式组合

auto result = std::views::numbers

| std::views::filter(is_prime)

| std::views::transform(square)

| std::views::take(10);

二、非修改序列操作算法

2.1 查找算法实战

// 1. find系列:线性查找

std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9};

// 基本查找

auto it = std::find(vec.begin(), vec.end(), 5);

if (it != vec.end()) {

std::cout << "找到5,位置:" << std::distance(vec.begin(), it) << std::endl;

}

// 条件查找

auto even_it = std::find_if(vec.begin(), vec.end(),

\[\](int x) { return x % 2 == 0; });

// 查找连续重复元素

std::vector<int> nums = {1, 2, 2, 2, 3, 4, 4, 5};

auto adj_it = std::adjacent_find(nums.begin(), nums.end());

// 返回第一个重复对 {2, 2} 的迭代器

// 2. search系列:子序列查找

std::string text = "the quick brown fox jumps over the lazy dog";

std::string pattern = "fox";

auto pos = std::search(text.begin(), text.end(),

pattern.begin(), pattern.end());

// 3. 二分查找(要求有序)

std::sort(vec.begin(), vec.end());

bool found = std::binary_search(vec.begin(), vec.end(), 5);

auto lower = std::lower_bound(vec.begin(), vec.end(), 5); // 第一个≥5的位置

auto upper = std::upper_bound(vec.begin(), vec.end(), 5); // 第一个>5的位置

2.2 计数与比较算法

// 1. 计数算法

std::vector<int> data = {1, 2, 3, 2, 4, 2, 5, 2, 6};

int count_2 = std::count(data.begin(), data.end(), 2); // 4

int count_even = std::count_if(data.begin(), data.end(),

\[\](int x) { return x % 2 == 0; }); // 5

// 2. 比较算法

std::vector<int> a = {1, 2, 3, 4, 5};

std::vector<int> b = {1, 2, 3, 4, 5};

std::vector<int> c = {1, 2, 3, 4, 6};

bool equal_ab = std::equal(a.begin(), a.end(), b.begin()); // true

bool equal_ac = std::equal(a.begin(), a.end(), c.begin()); // false

// 3. 不匹配查找(找第一个不同点)

auto mismatch_pair = std::mismatch(a.begin(), a.end(), c.begin());

if (mismatch_pair.first != a.end()) {

std::cout << "第一个不同点:a=" << *mismatch_pair.first

<< ", c=" << *mismatch_pair.second << std::endl;

}

三、修改序列操作算法

3.1 拷贝与移动算法

// 1. 拷贝算法

std::vector<int> src = {1, 2, 3, 4, 5};

std::vector<int> dst(5);

// 基本拷贝

std::copy(src.begin(), src.end(), dst.begin());

// 条件拷贝

std::vector<int> evens;

std::copy_if(src.begin(), src.end(), std::back_inserter(evens),

\[\](int x) { return x % 2 == 0; }); // {2, 4}

// 反向拷贝

std::vector<int> reversed(5);

std::copy(src.rbegin(), src.rend(), reversed.begin()); // {5, 4, 3, 2, 1}

// 2. 移动算法(C++11)

std::vector<std::unique_ptr<int>> src_ptrs;

for (int i = 0; i < 5; ++i) {

src_ptrs.push_back(std::make_unique<int>(i));

}

std::vector<std::unique_ptr<int>> dst_ptrs(5);

std::move(src_ptrs.begin(), src_ptrs.end(), dst_ptrs.begin());

// src_ptrs现在包含空unique_ptr

// 3. 安全拷贝算法(防止重叠)

std::vector<int> overlapping = {1, 2, 3, 4, 5, 6, 7, 8, 9};

// 将前5个元素向后移动3位

std::copy_backward(overlapping.begin(), overlapping.begin() + 5,

overlapping.begin() + 8);

// 结果:{1, 2, 3, 1, 2, 3, 4, 5, 9}

3.2 变换与替换算法

// 1. transform:元素转换

std::vector<int> numbers = {1, 2, 3, 4, 5};

std::vector<int> squares(numbers.size());

// 一元变换

std::transform(numbers.begin(), numbers.end(), squares.begin(),

\[\](int x) { return x * x; }); // {1, 4, 9, 16, 25}

// 二元变换(合并两个序列)

std::vector<int> a = {1, 2, 3, 4, 5};

std::vector<int> b = {10, 20, 30, 40, 50};

std::vector<int> sums(a.size());

std::transform(a.begin(), a.end(), b.begin(), sums.begin(),

std::plus<int>()); // {11, 22, 33, 44, 55}

// 2. replace系列

std::vector<int> data = {1, 2, 3, 2, 4, 2, 5};

// 替换所有等于2的元素为99

std::replace(data.begin(), data.end(), 2, 99); // {1, 99, 3, 99, 4, 99, 5}

// 条件替换

std::replace_if(data.begin(), data.end(),

\[\](int x) { return x > 3; }, 0); // {1, 0, 3, 0, 0, 0, 0}

// 3. fill算法

std::vector<int> vec(10);

std::fill(vec.begin(), vec.end(), 42); // 全部填充为42

std::fill_n(vec.begin() + 2, 5, 99); // 从第3个元素开始,填充5个99

3.3 删除与去重算法

// 重要:remove算法并不真正删除元素,而是移动元素

std::vector<int> numbers = {1, 2, 3, 2, 4, 2, 5, 2};

// 1. remove:移动要删除的元素到末尾,返回新的逻辑结尾

auto new_end = std::remove(numbers.begin(), numbers.end(), 2);

// numbers现在可能是:{1, 3, 4, 5, ?, ?, ?, ?},?表示未指定值

// 真正删除需要结合erase

numbers.erase(new_end, numbers.end()); // {1, 3, 4, 5}

// 2. remove_if:条件删除

std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9};

auto it = std::remove_if(data.begin(), data.end(),

\[\](int x) { return x % 2 == 0; }); // 删除偶数

data.erase(it, data.end()); // {1, 3, 5, 7, 9}

// 3. unique:去除连续重复

std::vector<int> dupes = {1, 1, 2, 2, 2, 3, 4, 4, 5};

auto unique_end = std::unique(dupes.begin(), dupes.end());

dupes.erase(unique_end, dupes.end()); // {1, 2, 3, 4, 5}

// 自定义比较的unique

std::vector<std::string> words = {"hello", "Hello", "HELLO", "world", "World"};

auto word_end = std::unique(words.begin(), words.end(),

\[\](const std::string& a, const std::string& b) {

std::string a_lower = a;

std::string b_lower = b;

std::transform(a_lower.begin(), a_lower.end(), a_lower.begin(), ::tolower);

std::transform(b_lower.begin(), b_lower.end(), b_lower.begin(), ::tolower);

return a_lower == b_lower;

});

words.erase(word_end, words.end()); // {"hello", "world"}

四、排序与相关算法

4.1 排序算法详解

// 1. 基本排序

std::vector<int> numbers = {5, 2, 8, 1, 9, 3, 7, 4, 6};

// 快速排序(不稳定)

std::sort(numbers.begin(), numbers.end()); // 升序

std::sort(numbers.begin(), numbers.end(), std::greater<int>()); // 降序

// 稳定排序(保持相等元素的原始顺序)

struct Student {

std::string name;

int score;

};

std::vector<Student> students = {

{"Alice", 85}, {"Bob", 85}, {"Charlie", 90}, {"David", 80}

};

std::stable_sort(students.begin(), students.end(),

\[\](const Student& a, const Student& b) {

return a.score > b.score; // 按分数降序

});

// 2. 部分排序

std::vector<int> data = {9, 8, 7, 6, 5, 4, 3, 2, 1};

// 部分排序:保证前5个是最小的,但不保证顺序

std::partial_sort(data.begin(), data.begin() + 5, data.end());

// 结果:前5个是{1, 2, 3, 4, 5},后4个未定义

// 3. 第n个元素(快速选择)

std::vector<int> nums = {9, 3, 6, 2, 7, 1, 8, 5, 4};

auto middle = nums.begin() + nums.size() / 2;

// 使得第n个元素就位,左边都≤它,右边都≥它

std::nth_element(nums.begin(), middle, nums.end());

std::cout << "中位数:" << *middle << std::endl; // 5

4.2 堆算法

// 堆操作(基于vector的优先队列)

std::vector<int> heap = {3, 1, 4, 1, 5, 9, 2, 6};

// 1. 建立最大堆

std::make_heap(heap.begin(), heap.end()); // {9, 5, 6, 1, 1, 4, 2, 3}

// 2. 添加元素

heap.push_back(7);

std::push_heap(heap.begin(), heap.end()); // 重新调整堆

// 3. 弹出堆顶

std::pop_heap(heap.begin(), heap.end()); // 最大元素移动到末尾

heap.pop_back(); // 移除最大元素

// 4. 堆排序

std::vector<int> data = {3, 1, 4, 1, 5, 9, 2, 6};

std::make_heap(data.begin(), data.end());

std::sort_heap(data.begin(), data.end()); // {1, 1, 2, 3, 4, 5, 6, 9}

4.3 集合算法

// 前提:两个输入序列必须已排序

std::vector<int> set1 = {1, 2, 3, 4, 5};

std::vector<int> set2 = {3, 4, 5, 6, 7};

std::vector<int> result;

// 1. 并集

std::set_union(set1.begin(), set1.end(),

set2.begin(), set2.end(),

std::back_inserter(result)); // {1, 2, 3, 4, 5, 6, 7}

// 2. 交集

result.clear();

std::set_intersection(set1.begin(), set1.end(),

set2.begin(), set2.end(),

std::back_inserter(result)); // {3, 4, 5}

// 3. 差集(set1 - set2)

result.clear();

std::set_difference(set1.begin(), set1.end(),

set2.begin(), set2.end(),

std::back_inserter(result)); // {1, 2}

// 4. 对称差集(只在其中一个集合中)

result.clear();

std::set_symmetric_difference(set1.begin(), set1.end(),

set2.begin(), set2.end(),

std::back_inserter(result)); // {1, 2, 6, 7}

// 5. 包含判断

bool includes = std::includes(set1.begin(), set1.end(),

set2.begin(), set2.begin() + 3); // 检查{3,4,5}是否在set1中

五、数值算法

5.1 累加与内积

// 1. accumulate:累加/累乘

std::vector<int> nums = {1, 2, 3, 4, 5};

// 基本累加

int sum = std::accumulate(nums.begin(), nums.end(), 0); // 15

// 累乘

int product = std::accumulate(nums.begin(), nums.end(), 1,

std::multiplies<int>()); // 120

// 字符串连接

std::vector<std::string> words = {"Hello", " ", "World", "!"};

std::string sentence = std::accumulate(words.begin(), words.end(),

std::string()); // "Hello World!"

// 2. inner_product:内积(点积)

std::vector<int> a = {1, 2, 3};

std::vector<int> b = {4, 5, 6};

int dot_product = std::inner_product(a.begin(), a.end(), b.begin(), 0);

// 计算:1*4 + 2*5 + 3*6 = 32

// 自定义操作的内积

int custom_product = std::inner_product(a.begin(), a.end(), b.begin(), 0,

std::plus<int>(), // 累加操作

\[\](int x, int y) { return (x - y) * (x - y); }); // 差值的平方

// 计算:(1-4)² + (2-5)² + (3-6)² = 27

5.2 部分和与相邻差

std::vector<int> nums = {1, 2, 3, 4, 5};

std::vector<int> result(nums.size());

// 1. partial_sum:部分和(前缀和)

std::partial_sum(nums.begin(), nums.end(), result.begin());

// result = {1, 3, 6, 10, 15}

// 自定义操作的部分和

std::partial_sum(nums.begin(), nums.end(), result.begin(),

std::multiplies<int>()); // 部分积

// result = {1, 2, 6, 24, 120}

// 2. adjacent_difference:相邻差

std::adjacent_difference(nums.begin(), nums.end(), result.begin());

// result = {1, 1, 1, 1, 1} (第一个元素保持原值)

// 自定义操作的相邻差

std::adjacent_difference(nums.begin(), nums.end(), result.begin(),

\[\](int a, int b) { return std::abs(a - b); }); // 绝对差

六、C++20 Ranges算法(现代C++)

6.1 Ranges视图管道

#include <ranges>

#include <algorithm>

namespace rv = std::views;

std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// 1. 链式操作(惰性求值)

auto result = numbers

| rv::filter(\[\](int x) { return x % 2 == 0; }) // 取偶数

| rv::transform(\[\](int x) { return x * x; }) // 平方

| rv::take(3); // 取前3个

// 转换为容器

std::vector<int> squares(std::ranges::begin(result),

std::ranges::end(result)); // {4, 16, 36}

// 2. 直接使用ranges算法

bool all_even = std::ranges::all_of(numbers,

\[\](int x) { return x % 2 == 0; }); // false

// 3. 投影(Projection)

struct Person {

std::string name;

int age;

};

std::vector<Person> people = {

{"Alice", 25}, {"Bob", 30}, {"Charlie", 20}, {"David", 35}

};

// 按年龄排序,使用投影

std::ranges::sort(people, {}, &Person::age);

// 等价于:std::ranges::sort(people, \[\](const Person& a, const Person& b) { return a.age < b.age; });

6.2 范围适配器组合

// 复杂的管道操作示例

std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

auto processed = data

| rv::drop(2) // 跳过前2个:{3,4,5,6,7,8,9,10}

| rv::take(6) // 取6个:{3,4,5,6,7,8}

| rv::filter(\[\](int x) {

return x % 2 == 1; // 取奇数:{3,5,7}

})

| rv::transform(\[\](int x) {

return x * 10; // 乘以10:{30,50,70}

})

| rv::reverse(); // 反转:{70,50,30}

// 惰性求值:直到迭代时才计算

for (int x : processed) {

std::cout << x << " "; // 输出:70 50 30

}

七、性能优化与最佳实践

7.1 算法选择指南

// 根据需求选择合适的算法

void process_data(const std::vector<int>& data) {

// 1. 只需要检查是否存在 → find

bool has_negative = std::find_if(data.begin(), data.end(),

\[\](int x) { return x < 0; }) != data.end();

// 2. 需要计数 → count

int negative_count = std::count_if(data.begin(), data.end(),

\[\](int x) { return x < 0; });

// 3. 需要检查所有/任意元素满足条件 → all_of/any_of

bool all_positive = std::all_of(data.begin(), data.end(),

\[\](int x) { return x > 0; });

// 4. 需要排序 → 根据稳定性选择

std::vector<int> sorted = data;

if (need_stable_sort) {

std::stable_sort(sorted.begin(), sorted.end());

} else {

std::sort(sorted.begin(), sorted.end());

}

// 5. 需要部分排序 → nth_element或partial_sort

std::vector<int> copy = data;

auto mid = copy.begin() + copy.size() / 2;

std::nth_element(copy.begin(), mid, copy.end()); // 找中位数

}

7.2 避免常见陷阱

// 陷阱1:迭代器失效

std::vector<int> vec = {1, 2, 3, 4, 5};

auto it = std::find(vec.begin(), vec.end(), 3);

// 错误:在修改容器后使用旧迭代器

vec.erase(it); // it现在可能失效

// 正确:立即使用或重新获取

it = vec.erase(it); // erase返回新的有效迭代器

// 陷阱2:错误使用remove

std::vector<int> numbers = {1, 2, 3, 2, 4, 2, 5};

// 错误:以为remove会删除元素

std::remove(numbers.begin(), numbers.end(), 2);

// numbers大小不变!只是移动了元素

// 正确:erase-remove惯用法

numbers.erase(

std::remove(numbers.begin(), numbers.end(), 2),

numbers.end()

);

// 陷阱3:未排序就使用二分查找

std::vector<int> unsorted = {5, 3, 1, 4, 2};

// 错误:对未排序序列使用binary_search

bool found = std::binary_search(unsorted.begin(), unsorted.end(), 3); // 未定义行为

// 正确:先排序

std::sort(unsorted.begin(), unsorted.end());

found = std::binary_search(unsorted.begin(), unsorted.end(), 3); // 正确

7.3 自定义算法扩展

// 实现自己的算法(遵循STL风格)

template<typename ForwardIt, typename BinaryPredicate>

ForwardIt unique_erase(ForwardIt first, ForwardIt last, BinaryPredicate p) {

if (first == last) return last;

ForwardIt result = first;

while (++first != last) {

if (!p(*result, *first) && ++result != first) {

*result = std::move(*first);

}

}

return ++result;

}

// 使用示例

std::vector<std::string> words = {"apple", "Apple", "APPLE", "banana", "Banana"};

auto new_end = unique_erase(words.begin(), words.end(),

\[\](const std::string& a, const std::string& b) {

std::string a_lower, b_lower;

std::transform(a.begin(), a.end(), std::back_inserter(a_lower), ::tolower);

std::transform(b.begin(), b.end(), std::back_inserter(b_lower), ::tolower);

return a_lower == b_lower;

});

words.erase(new_end, words.end()); // {"apple", "banana"}

希望以上内容对你有帮助。

如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。

相关推荐
凡人叶枫11 小时前
Effective C++ 条款16:成对使用 new 和 delete 时要采取相同形式
开发语言·c++·effective c++
不吃土豆的马铃薯12 小时前
C++ 高性能网络缓冲区 Buffer 源码解析
linux·服务器·开发语言·网络·c++
.千余12 小时前
【C++】C++继承入门(下):友元、静态成员与菱形继承的底层逻辑
开发语言·c++·笔记·学习·其他
初中就开始混世的大魔王13 小时前
6 Fast DDS-传输层
开发语言·c++·中间件·信息与通信
代码中介商15 小时前
C++ 智能指针完全指南(三):weak_ptr 与循环引用
开发语言·c++
BestOrNothing_201515 小时前
ROS2 C++ 小车控制完整实战(二):自定义 msg 消息发布与订阅保姆级教程
c++·ros2·subscriber·publisher·msg·topic通信·自定义接口
-森屿安年-15 小时前
91. 解码方法
c++·动态规划
有点。15 小时前
C++(二分答案)
c++
程序喵大人15 小时前
【C++并发系列】第一章:多线程读写同一个变量为什么会出错
开发语言·c++·多线程·并发
梓䈑16 小时前
C++ 接入 SQLite 数据库:环境搭建、API 详解 与 两种执行方式对比
数据库·c++·sqlite