1. C++标准库算法概述
在C++开发中,标准库算法是我们日常编码的利器。这些算法主要定义在
标准库算法大致可分为以下几类:
- 非修改序列算法:不改变容器内容,如查找、计数等
- 修改序列算法:会改变容器内容,如排序、替换等
- 排序和相关算法:包括各种排序和二分查找
- 数值算法:专门用于数值计算
- 堆算法:用于构建和操作堆结构
这些算法都采用迭代器作为参数,这使得它们可以应用于各种容器(vector、list、deque等),甚至原生数组。这种设计体现了C++的泛型编程思想,也是STL强大灵活性的体现。
2. 非修改序列算法详解
2.1 查找算法
查找算法是最常用的非修改序列算法,主要包括find系列和search系列。
2.1.1 find和find_if
find用于查找特定值,而find_if则可以根据谓词条件查找:
cpp复制vector<int> data = {1, 3, 5, 7, 9};
// 查找值为5的元素
auto it = find(data.begin(), data.end(), 5);
if (it != data.end()) {
cout << "Found at position: " << distance(data.begin(), it) << endl;
}
// 查找第一个大于6的元素
auto it2 = find_if(data.begin(), data.end(), [](int x) {
return x > 6;
});
提示:对于自定义类型,需要重载==运算符或提供自定义谓词函数才能使用find。
2.1.2 find_end和search
find_end用于查找子序列最后一次出现的位置,而search则是查找第一次出现的位置:
cpp复制vector<int> main = {1,2,3,4,1,2,3};
vector<int> sub = {1,2};
// 查找最后一次出现的位置
auto last_pos = find_end(main.begin(), main.end(), sub.begin(), sub.end());
// 查找第一次出现的位置
auto first_pos = search(main.begin(), main.end(), sub.begin(), sub.end());
2.2 计数算法
count和count_if用于统计满足条件的元素数量:
cpp复制vector<int> nums = {1, 2, 3, 2, 4, 2};
// 统计2出现的次数
int cnt = count(nums.begin(), nums.end(), 2);
// 统计偶数的数量
int even_cnt = count_if(nums.begin(), nums.end(), [](int x) {
return x % 2 == 0;
});
2.3 遍历算法
for_each是最常用的遍历算法,可以对每个元素执行操作:
cpp复制vector<int> vec = {1, 2, 3, 4, 5};
// 每个元素加1
for_each(vec.begin(), vec.end(), [](int& x) {
x += 1;
});
注意:for_each不会改变容器大小,但可以修改元素内容。如果需要纯遍历,可以考虑C++11的范围for循环。
2.4 比较算法
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());
// 查找第一个不匹配的位置
auto mismatch_pair = mismatch(a.begin(), a.end(), b.begin());
if (mismatch_pair.first != a.end()) {
cout << "First mismatch: " << *mismatch_pair.first
<< " vs " << *mismatch_pair.second << endl;
}
2.5 条件检查算法
all_of、any_of和none_of用于检查序列是否满足特定条件:
cpp复制vector<int> data = {2, 4, 6, 8};
// 是否都是偶数
bool allEven = all_of(data.begin(), data.end(), [](int x) {
return x % 2 == 0;
});
// 是否存在奇数
bool anyOdd = any_of(data.begin(), data.end(), [](int x) {
return x % 2 != 0;
});
// 是否没有负数
bool noNegative = none_of(data.begin(), data.end(), [](int x) {
return x < 0;
});
3. 修改序列算法详解
3.1 复制算法
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;
});
提示:使用back_inserter可以自动处理目标容器空间不足的情况,它会调用push_back添加元素。
3.2 变换算法
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;
});
// 双序列操作
vector<int> a = {1, 2, 3};
vector<int> b = {4, 5, 6};
vector<int> result(a.size());
transform(a.begin(), a.end(), b.begin(), result.begin(), [](int x, int y) {
return x + y;
});
3.3 替换算法
replace系列算法用于替换元素:
cpp复制vector<int> data = {1, 2, 3, 2, 5};
// 简单替换
replace(data.begin(), data.end(), 2, 20);
// 条件替换
replace_if(data.begin(), data.end(), [](int x) {
return x > 10;
}, 0);
// 复制时替换
vector<int> output;
replace_copy(data.begin(), data.end(), back_inserter(output), 3, 300);
3.4 删除算法
remove系列算法用于"删除"元素(实际是移动):
cpp复制vector<int> nums = {1, 2, 3, 2, 4};
// 逻辑删除
auto new_end = remove(nums.begin(), nums.end(), 2);
// 物理删除
nums.erase(new_end, nums.end());
// 条件删除
nums.erase(remove_if(nums.begin(), nums.end(), [](int x) {
return x % 2 == 0;
}), nums.end());
重要:remove只是把要删除的元素移到末尾并返回新的逻辑终点,必须配合erase才能真正删除元素。
3.5 其他修改算法
unique用于去除连续重复元素:
cpp复制vector<int> data = {1, 1, 2, 2, 3, 3, 3, 4, 5};
auto last = unique(data.begin(), data.end());
data.erase(last, data.end());
reverse用于反转序列:
cpp复制vector<int> vec = {1, 2, 3, 4, 5};
reverse(vec.begin(), vec.end());
rotate用于旋转序列:
cpp复制vector<int> nums = {1, 2, 3, 4, 5};
rotate(nums.begin(), nums.begin() + 2, nums.end());
// 结果:3,4,5,1,2
shuffle用于随机打乱序列:
cpp复制vector<int> cards = {1, 2, 3, 4, 5};
random_device rd;
mt19937 g(rd());
shuffle(cards.begin(), cards.end(), g);
4. 排序和相关算法
4.1 基本排序算法
sort是最常用的排序算法:
cpp复制vector<int> data = {5, 3, 1, 4, 2};
// 默认升序
sort(data.begin(), data.end());
// 降序
sort(data.begin(), data.end(), greater<int>());
// 自定义排序
sort(data.begin(), data.end(), [](int a, int b) {
return a > b;
});
stable_sort保证相等元素的原始顺序:
cpp复制vector<pair<int, int>> items = {{1,2}, {2,1}, {1,1}};
stable_sort(items.begin(), items.end(), [](auto& a, auto& b) {
return a.first < b.first;
});
partial_sort用于部分排序:
cpp复制vector<int> nums = {5, 3, 1, 4, 2, 6};
partial_sort(nums.begin(), nums.begin() + 3, nums.end());
// 前三个元素是排序后的最小三个数
4.2 二分查找算法
二分查找要求序列已排序:
cpp复制vector<int> sorted = {1, 3, 3, 5, 7};
// 简单查找
bool found = binary_search(sorted.begin(), sorted.end(), 3);
// 下界和上界
auto lower = lower_bound(sorted.begin(), sorted.end(), 3);
auto upper = upper_bound(sorted.begin(), sorted.end(), 3);
// 获取范围
auto range = equal_range(sorted.begin(), sorted.end(), 3);
4.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());
5. 堆算法
堆算法可以把序列当作二叉堆操作:
cpp复制vector<int> nums = {4, 1, 3, 2, 5};
// 建堆
make_heap(nums.begin(), nums.end());
// 添加元素
nums.push_back(6);
push_heap(nums.begin(), nums.end());
// 弹出堆顶
pop_heap(nums.begin(), nums.end());
int max_val = nums.back();
nums.pop_back();
// 堆排序
sort_heap(nums.begin(), nums.end());
6. 数值算法
数值算法定义在
6.1 accumulate
累加或自定义归约操作:
cpp复制vector<int> data = {1, 2, 3, 4, 5};
// 求和
int sum = accumulate(data.begin(), data.end(), 0);
// 求积
int product = accumulate(data.begin(), data.end(), 1, multiplies<int>());
6.2 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);
// 自定义操作
int result = inner_product(a.begin(), a.end(), b.begin(), 0,
plus<int>(), [](int x, int y) { return x * y * 2; });
6.3 partial_sum和adjacent_difference
计算部分和和相邻差值:
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
// 相邻差值
adjacent_difference(src.begin(), src.end(), dst.begin());
// dst: 1,1,1,1,1
6.4 iota
填充递增序列:
cpp复制vector<int> seq(5);
iota(seq.begin(), seq.end(), 10);
// seq: 10,11,12,13,14
7. 算法使用经验与技巧
7.1 算法选择指南
- 需要简单查找时:find/find_if
- 需要高效查找已排序序列:binary_search/lower_bound
- 需要修改元素时:transform/replace
- 需要删除元素时:remove-erase惯用法
- 需要排序时:sort/stable_sort
- 需要部分排序时:partial_sort/nth_element
7.2 性能考量
- sort平均O(n log n),但最坏O(n²),如果需要保证性能可用stable_sort
- 对于小型序列(<=20元素),插入排序可能更快
- 多次查找时,先排序再用二分查找更高效
- remove等算法只是移动元素,真正删除需要erase
7.3 自定义比较
许多算法支持自定义比较函数:
cpp复制// 自定义排序
sort(data.begin(), data.end(), [](const auto& a, const auto& b) {
return a.property < b.property;
});
// 自定义查找条件
auto it = find_if(data.begin(), data.end(), [](const auto& x) {
return x.value > threshold;
});
7.4 算法组合
算法可以组合使用实现复杂功能:
cpp复制// 删除所有满足条件的元素
data.erase(remove_if(data.begin(), data.end(), predicate), data.end());
// 转换后过滤
vector<int> result;
copy_if(
make_transform_iterator(data.begin(), transform_func),
make_transform_iterator(data.end(), transform_func),
back_inserter(result),
filter_predicate
);
8. 常见问题与解决方案
8.1 迭代器失效问题
在修改容器时要注意迭代器失效:
cpp复制vector<int> data = {1, 2, 3, 4, 5};
auto it = data.begin() + 2;
// 可能导致it失效的操作
data.push_back(6); // 可能引起重新分配
data.erase(data.begin()); // 元素移动
// 安全做法:操作后重新获取迭代器
it = data.begin() + 2;
8.2 谓词设计注意事项
谓词函数应该:
- 无副作用(不修改外部状态)
- 对于相同输入返回相同结果
- 简单高效(可能被多次调用)
8.3 容器选择建议
- 随机访问多的场景:vector/deque
- 频繁插入删除:list/forward_list
- 需要快速查找:set/map
- 需要保持有序:set/map或多重变体
8.4 算法复杂度误区
不是所有算法都是高效的:
- find/find_if是O(n)
- sort是O(n log n)
- remove是O(n)但不改变容器大小
- 某些操作如vector中间插入是O(n)
9. 现代C++中的算法增强
C++11/14/17/20对算法有诸多增强:
9.1 并行算法
C++17引入了并行执行策略:
cpp复制vector<int> bigData(1000000);
// 并行排序
sort(execution::par, bigData.begin(), bigData.end());
// 并行转换
transform(execution::par_unseq,
bigData.begin(), bigData.end(),
bigData.begin(), [](int x) { return x * 2; });
9.2 新算法
C++11/17/20新增了一些算法:
cpp复制// C++11
all_of, any_of, none_of
copy_if, copy_n
move, move_backward
shuffle
is_sorted, is_heap
// C++17
sample, for_each_n
clamp
gcd, lcm
// C++20
shift_left, shift_right
starts_with, ends_with
9.3 范围算法
C++20引入了范围概念,简化算法调用:
cpp复制vector<int> data = {1, 2, 3, 4, 5};
// 传统方式
sort(data.begin(), data.end());
// C++20范围方式
ranges::sort(data);
// 直接操作视图
auto even = data | views::filter([](int x) { return x % 2 == 0; });
10. 实际项目中的应用案例
10.1 数据清洗
cpp复制// 移除无效数据点
sensorReadings.erase(
remove_if(sensorReadings.begin(), sensorReadings.end(),
[](const auto& reading) {
return !reading.isValid();
}),
sensorReadings.end()
);
// 标准化数据
transform(sensorReadings.begin(), sensorReadings.end(),
sensorReadings.begin(), [](double val) {
return (val - minVal) / (maxVal - minVal);
});
10.2 统计分析
cpp复制// 计算平均值
double sum = accumulate(data.begin(), data.end(), 0.0);
double mean = sum / data.size();
// 计算中位数
nth_element(data.begin(), data.begin() + data.size()/2, data.end());
double median = data[data.size()/2];
// 计算众数
sort(data.begin(), data.end());
auto [maxIt, count] = max_element(
data.begin(), data.end(),
[current = data.front(), count = 0](auto a, auto b) mutable {
// 自定义比较逻辑计算频率
}
);
10.3 游戏开发
cpp复制// 按距离排序游戏对象
sort(gameObjects.begin(), gameObjects.end(),
[playerPos](const auto& a, const auto& b) {
return distance(a.position, playerPos)
< distance(b.position, playerPos);
});
// 移除不可见对象
levelObjects.erase(
remove_if(levelObjects.begin(), levelObjects.end(),
[viewport](const auto& obj) {
return !obj.isVisibleIn(viewport);
}),
levelObjects.end()
);
10.4 网络编程
cpp复制// 处理接收到的数据包
vector<Packet> packets = receivePackets();
// 按序列号排序
sort(packets.begin(), packets.end(),
[](const Packet& a, const Packet& b) {
return a.sequenceNumber < b.sequenceNumber;
});
// 查找丢失的包
auto lost = mismatch(
expectedSequence.begin(), expectedSequence.end(),
packets.begin(), packets.end(),
[](uint32_t expected, const Packet& p) {
return expected == p.sequenceNumber;
}
);
11. 性能优化技巧
11.1 避免不必要的拷贝
cpp复制// 不好的做法:创建临时vector
vector<int> temp = filter(data);
process(temp);
// 好的做法:使用视图或原地修改
auto filtered = data | views::filter(predicate);
process_ranges(filtered.begin(), filtered.end());
11.2 预分配内存
cpp复制vector<int> result;
// 预知大小时应预留空间
result.reserve(data.size());
// 使用back_inserter自动处理
copy_if(data.begin(), data.end(),
back_inserter(result), predicate);
11.3 算法选择优化
cpp复制// 只需要前N个元素时
partial_sort(data.begin(), data.begin() + N, data.end());
// 只需要第N个元素时
nth_element(data.begin(), data.begin() + N, data.end());
11.4 并行化处理
cpp复制// 传统串行处理
transform(data.begin(), data.end(), output.begin(), func);
// 并行处理(C++17)
transform(execution::par,
data.begin(), data.end(),
output.begin(), func);
12. 测试与调试建议
12.1 单元测试算法
cpp复制TEST(AlgorithmTests, TestFindIf) {
vector<int> data = {1, 3, 5, 7, 9};
auto it = find_if(data.begin(), data.end(),
[](int x) { return x > 6; });
ASSERT_NE(it, data.end());
EXPECT_EQ(*it, 7);
}
12.2 边界条件检查
cpp复制// 测试空容器
vector<int> empty;
auto it = find(empty.begin(), empty.end(), 42);
ASSERT_EQ(it, empty.end());
// 测试未找到情况
vector<int> data = {1, 2, 3};
it = find(data.begin(), data.end(), 4);
ASSERT_EQ(it, data.end());
12.3 性能测试
cpp复制// 测试不同算法的性能
BENCHMARK("std::sort", [](benchmark::State& state) {
vector<int> data = generateTestData();
for (auto _ : state) {
sort(data.begin(), data.end());
}
});
BENCHMARK("std::stable_sort", [](benchmark::State& state) {
vector<int> data = generateTestData();
for (auto _ : state) {
stable_sort(data.begin(), data.end());
}
});
13. 跨平台注意事项
13.1 随机数生成
cpp复制// 使用<random>而不是rand()
random_device rd;
mt19937 gen(rd());
uniform_int_distribution<> dis(1, 6);
// 安全使用shuffle
shuffle(data.begin(), data.end(), gen);
13.2 浮点比较
cpp复制// 避免直接比较浮点数
vector<double> floats = {...};
auto it = find_if(floats.begin(), floats.end(),
[target](double x) {
return abs(x - target) < epsilon;
});
13.3 内存对齐
cpp复制// 确保数据对齐以提高算法性能
vector<AlignedType> data;
data.reserve(count);
// 或者使用特殊分配器
vector<AlignedType, AlignedAllocator<AlignedType>> alignedData;
14. 未来发展趋势
14.1 范围库的普及
C++20的范围库将改变我们使用算法的方式:
cpp复制// 传统方式
sort(data.begin(), data.end());
// 范围方式
ranges::sort(data);
// 管道操作符
auto result = data | views::filter(isEven)
| views::transform(square);
14.2 概念约束
概念(Concepts)使算法接口更安全:
cpp复制template<input_iterator I, sentinel_for<I> S, class T>
requires equality_comparable_with<iter_value_t<I>, T>
I find(I first, S last, const T& value);
14.3 并行与并发
并行算法将继续发展,支持更多执行策略和异构计算。
14.4 定制点与扩展性
未来算法将提供更多定制点,允许用户扩展和特化算法行为。