数值算法与生成算法:iota、accumulate(进阶)、inner_product、partial_sum、adjacent_difference
数值算法这块,说实话,很多C++开发者平时用得不多。但一旦你开始处理数据统计、信号处理或者金融计算,这些算法就成了你的左膀右臂。我个人习惯是把它们分成两类:一类是生成器,比如 iota;另一类是累加器,比如 accumulate、inner_product 这些。今天咱们就把它们挨个捋一遍。
1. iota:连续值的填充利器
std::iota 这个函数名字挺怪,其实它来自希腊字母 ι(约塔),意思是“最小的增量”。它的作用很简单:从一个起始值开始,依次递增填充区间。
#include <numeric>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v(5);
std::iota(v.begin(), v.end(), 10);
// v 现在为 {10, 11, 12, 13, 14}
for (int x : v) std::cout << x << " ";
return 0;
}
你可能会问:这玩意儿有啥用?我在项目中遇到过需要生成一组连续的索引值,然后并行处理。用 iota 一行搞定,比手写循环干净多了。
2. accumulate:不止是求和
std::accumulate 大家应该不陌生,但很多人只拿它做累加。其实它的第二个版本支持自定义二元操作,能干很多事。
#include <numeric>
#include <vector>
#include <string>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// 基本求和
int sum = std::accumulate(v.begin(), v.end(), 0);
// 自定义操作:连乘
int product = std::accumulate(v.begin(), v.end(), 1,
[](int a, int b) { return a * b; });
// 字符串拼接
std::vector<std::string> words = {"Hello", " ", "World"};
std::string result = std::accumulate(words.begin(), words.end(), std::string{});
// result = "Hello World"
return 0;
}
0(int),即使容器里是 double,结果也会被截断。我曾经因为这个 bug 排查了半天——一个金融计算少了几毛钱,但累积起来就大了。
我个人建议:如果做浮点累加,初始值写成 0.0 或者直接用 double{}。别嫌麻烦,这能省掉很多隐式转换的坑。
3. inner_product:内积的优雅实现
内积,说白了就是两个序列对应元素相乘再求和。这在机器学习、信号处理里太常见了。
#include <numeric>
#include <vector>
int main() {
std::vector<double> a = {1.0, 2.0, 3.0};
std::vector<double> b = {4.0, 5.0, 6.0};
double dot = std::inner_product(a.begin(), a.end(), b.begin(), 0.0);
// dot = 1*4 + 2*5 + 3*6 = 32.0
return 0;
}
它的第四个和第五个参数可以自定义“加”和“乘”的操作。比如你想算加权和,或者用别的运算代替乘法,都可以。
a.size() == b.size(),或者用 std::distance 确认。
4. partial_sum:前缀和的妙用
前缀和(也叫部分和)在算法竞赛和数据分析里是经典技巧。std::partial_sum 把每一步的累加结果都存下来。
#include <numeric>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
std::vector<int> result(v.size());
std::partial_sum(v.begin(), v.end(), result.begin());
// result = {1, 3, 6, 10, 15}
for (int x : result) std::cout << x << " ";
return 0;
}
你想想看,如果我要频繁查询某个区间的和,用前缀和就能做到 O(1) 查询。我在做实时数据流统计时经常用这个——先算好前缀和,后面查起来飞快。
5. adjacent_difference:差分与变化量
这个算法和 partial_sum 是“逆运算”。它计算相邻元素的差值,常用于检测变化趋势或数据压缩。
#include <numeric>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {1, 3, 6, 10, 15};
std::vector<int> diff(v.size());
std::adjacent_difference(v.begin(), v.end(), diff.begin());
// diff = {1, 2, 3, 4, 5}
// 注意:第一个元素是原值,后面才是差值
for (int x : diff) std::cout << x << " ";
return 0;
}
diff.begin() 或者用 std::next 处理。
我曾经用 adjacent_difference 做股票价格的变化量分析。嗯,虽然没赚到钱,但代码写得挺顺的。
知识体系总览
下面这张图帮你理清这几个算法的关系:
实战建议
这几个算法在实际项目中怎么选?我一般这样判断:
- 需要生成连续序列 →
iota,比手写循环更语义化 - 只要一个最终结果 →
accumulate,注意初始值类型 - 需要两个序列的对应运算 →
inner_product,适合向量点积 - 需要所有中间累加值 →
partial_sum,适合做前缀和查询 - 需要相邻变化量 →
adjacent_difference,适合做差分分析
好了,数值算法这块就聊到这儿。下次你写循环累加的时候,不妨试试用 STL 算法替代——你会发现代码质量提升不少。