STL中partial_sum
算法的实际用途是什么/在哪里?
还有哪些其他有趣/非平凡的例子或用例?
答案 0 :(得分:22)
我用它来减少玩具lambda演算解释器中一个简单的标记扫描垃圾收集器的内存使用量。
GC池是一个大小相同的对象数组。目标是消除未链接到其他对象的对象,并将剩余的对象压缩到数组的开头。由于对象在内存中移动,因此需要更新每个链接。这需要一个对象重映射表。
partial_sum
允许表以压缩格式存储(每个对象只有一位),直到扫描完成并释放内存。由于对象很小,这大大减少了内存使用。
remove_if
将标记的对象压缩到池的开头。partial_sum
以生成指向新池的指针/索引表。
对数据缓存特别友好,将重映射表放在刚刚释放的内存中,因此仍然很热。
答案 1 :(得分:9)
关于部分和的一点要注意的是,它是一个操作,它可以解除相邻的差异 - 就像 - undoes +。或者更好的是,如果你还记得微积分,那么差异化就会消除整合。更好,因为相邻差异本质上是差异化,部分和是整合。
假设您已经模拟了汽车,并且在每个步骤中您都需要知道位置,速度和加速度。您只需存储其中一个值,因为您可以计算其他两个值。假设您在每个时间步都存储位置,您可以采用相邻的位置差来给出速度和速度的相邻差值来给出加速度。或者,如果存储加速度,则可以采用部分和来给出速度,速度的部分和给出位置。
部分总和是大多数人不会经常出现的功能之一,但在找到合适的情况时非常有用。很像微积分。
答案 2 :(得分:7)
上次我(将要)使用它是将离散概率分布(p(X = k)的数组)转换为累积分布(p(X <= k)的数组)。要从分布中选择一次,您可以随机选择[0-1]中的数字,然后二进制搜索到累积分布。
但是,这段代码不是用C ++编写的,所以我自己做了部分求和。
答案 3 :(得分:6)
您可以使用它来生成单调递增的数字序列。例如,以下内容生成包含数字1到42的vector
:
std::vector<int> v(42, 1);
std::partial_sum(v.begin(), v.end(), v.begin());
这是日常用例吗?可能不是,虽然我发现它在几个场合都很有用。
您还可以使用std::partial_sum
生成因子列表。 (但这更不实用,因为可以用典型的整数数据类型表示的因子数量非常有限。虽然很有趣,但是:-D)
std::vector<int> v(10, 1);
std::partial_sum(v.begin(), v.end(), v.begin());
std::partial_sum(v.begin(), v.end(), v.begin(), std::multiplies<int>());
答案 4 :(得分:3)
个人使用案例:轮盘赌选择
我在轮盘赌选择算法(link text)中使用partial_sum
。该算法从容器中随机选择元素,其概率与之前给定的某个值成线性关系。
因为我所有的元素都可以选择带来一个不一定标准化的值,所以我使用partial_sum
算法来构造类似“轮盘赌”的东西,因为我总结了所有的元素。然后我在这个范围内选择了一个随机变量(最后partial_sum
是所有的总和)并使用stl::lower_bound
搜索我的随机搜索着陆的“轮子”。 lower_bound
算法返回的元素是选定的元素。
除了使用partial_sum
的清晰和富有表现力的代码的优势之外,我还可以在尝试GCC并行模式时获得一些速度,该模式为某些算法带来了并行版本,其中一个是partial_sum(link text)。
我所知道的另一种用法:并行处理中最重要的算法原语之一(但可能与STL稍微偏离)
如果您对使用partial_sum
的重度优化算法感兴趣(在这种情况下,在同义词“scan”或“prefix_sum”下可能会有更多结果),那么请转到并行算法社区。他们总是需要它。在不使用quicksort或mergesort的情况下,您将找不到基于GPU或CUDA的并行排序算法。此操作是使用的最重要的并行原语之一。我认为它最常用于计算动态算法中的偏移量。考虑快速排序中的分区步骤,将其拆分并送入并行线程。在计算分区之前,您不知道分区的每个槽中的元素数。因此,您需要为所有线程提供一些偏移以供以后访问。
也许您会在Chapter 39. Parallel Prefix Sum (Scan) with CUDA处理这个热门话题中找到更多信息。一篇关于Nvidia {{3}}的简短文章和一些带有一些应用示例的scan原语,您将在 {{3}} 中找到。
答案 5 :(得分:0)
你可以建立一个“移动总和”(移动平均线的前身):
template <class T>
void moving_sum (const vector<T>& in, int num, vector<T>& out)
{
// cummulative sum
partial_sum (in.begin(), in.end(), out.begin());
// shift and subtract
int j;
for (int i = out.size() - 1; i >= 0; i--) {
j = i - num;
if (j >= 0)
out[i] -= out[j];
}
}
然后用:
来调用它vector<double> v(10);
// fill in v
vector<double> v2 (v.size());
moving_sum (v, 3, v2);
答案 6 :(得分:0)
你知道,我确实曾经使用过 partial_sum() ...这是我在求职面试中被问到的这个有趣的小问题。我非常喜欢它,我回家编码了。
问题是:给定一个连续的整数序列,找到具有最高值的最短子序列。例如。给出:
Value: -1 2 3 -1 4 -2 -4 5
Index: 0 1 2 3 4 5 6 7
我们会找到子序列[1,4]
现在明显的解决方案是运行3 for 循环,迭代所有可能的启动&amp;结束,并依次将每个可能的子序列的值相加。效率低下,但编码速度快,很难犯错误。 (特别是当第三个 for 循环只是累积(开始,结束,0)时。)
正确的解决方案涉及分而治之/自下而上的方法。例如。将问题空间分成两半,并且每半个计算该部分中包含的最大子序列,最大子序列包括起始编号,最大子序列(包括结束编号)和整个部分的子序列。有了这些数据,我们就可以将这两个部分组合在一起,而无需对任何一个进行任何进一步的评估。显然,每一半的数据可以通过进一步将每一半分成两半(四分之一),每四分之一分成两半(八分之一)来计算,依此类推,直到我们有一些琐碎的单例情况。这一切都非常有效。
但除此之外,我还想探索第三种(效率稍低)选项。它与 3-for-loop case 类似,只是我们添加相邻的数字以避免这么多的工作。我们的想法是,当我们可以添加t1 = a + b,t2 = t1 + c和t3 = t2 + d时,不需要添加+ b,a + b + c和a + b + c + d。这是一个空间/计算权衡的事情。它的工作原理是改变序列:
Index: 0 1 2 3 4
FROM: 1 2 3 4 5
TO: 1 3 6 10 15
从而为我们提供从index = 0开始到index = 0,1,2,3,4结束的所有可能的子串。
然后我们迭代这个集合,减去连续可能的“开始”点......
FROM: 1 3 6 10 15
TO: - 2 5 9 14
TO: - - 3 7 12
TO: - - - 4 9
TO: - - - - 5
从而为我们提供所有可能子序列的值(总和)。
我们可以通过 max_element()找到每次迭代的最大值。
第一步最容易通过 partial_sum()完成。
其余步骤通过 for 循环和转换(data + i,data + size,data + i,bind2nd(减去&lt; TYPE&gt;(),data [i-1] ))
显然是O(N ^ 2)。但仍然有趣而有趣......
答案 7 :(得分:0)
部分和通常在并行算法中很有用。考虑代码
for (int i=0; N>i; ++i) {
sum += x[i];
do_something(sum);
}
如果要并行化此代码,则需要知道部分总和。我正在使用GNU并行版本的partial_sum来实现非常相似的东西。
答案 8 :(得分:0)
我经常使用部分和来不求和,而是根据前一个计算序列中的当前值。
例如,如果您集成了一个功能。每个新步骤都是上一步,vt += dvdt
或vt = integrate_step(dvdt, t_prev, t_prev+dt);
。
答案 9 :(得分:0)
个人使用案例:从CLRS计算排序的中间步骤:
COUNTING_SORT (A, B, k)
for i ← 1 to k do
c[i] ← 0
for j ← 1 to n do
c[A[j]] ← c[A[j]] + 1
//c[i] now contains the number of elements equal to i
// std::partial_sum here
for i ← 2 to k do
c[i] ← c[i] + c[i-1]
// c[i] now contains the number of elements ≤ i
for j ← n downto 1 do
B[c[A[i]]] ← A[j]
c[A[i]] ← c[A[j]] - 1
答案 10 :(得分:0)
在非参数贝叶斯方法中,有一个Metropolis-Hastings步骤(每次观察)确定对新的或现有的群集进行采样。如果必须对现有群集进行采样,则需要使用不同的权重。在以下示例代码中模拟这些加权似然。
#include <random>
#include <iostream>
#include <algorithm>
int main() {
std::default_random_engine generator(std::random_device{}());
std::uniform_real_distribution<double> distribution(0.0,1.0);
int K = 8;
std::vector<double> weighted_likelihood(K);
for (int i = 0; i < K; ++i) {
weighted_likelihood[i] = i*10;
}
std::cout << "Weighted likelihood: ";
for (auto i: weighted_likelihood) std::cout << i << ' ';
std::cout << std::endl;
std::vector<double> cumsum_likelihood(K);
std::partial_sum(weighted_likelihood.begin(), weighted_likelihood.end(), cumsum_likelihood.begin());
std::cout << "Cumulative sum of weighted likelihood: ";
for (auto i: cumsum_likelihood) std::cout << i << ' ';
std::cout << std::endl;
std::vector<int> frequency(K);
int N = 280000;
for (int i = 0; i < N; ++i) {
double pick = distribution(generator) * cumsum_likelihood.back();
auto lower = std::lower_bound(cumsum_likelihood.begin(), cumsum_likelihood.end(), pick);
int index = std::distance(cumsum_likelihood.begin(), lower);
frequency[index]++;
}
std::cout << "Frequencies: ";
for (auto i: frequency) std::cout << i << ' ';
std::cout << std::endl;
}
请注意,这与https://stackoverflow.com/users/13005/steve-jessop的答案没有什么不同。它被添加以提供关于特定情况的更多上下文(非参数贝叶斯方法,例如Neal使用Dirichlet过程作为先验的算法)以及将partial_sum
与lower_bound
组合使用的实际代码。 / p>