高效组合昂贵的卷帘窗产品

时间:2019-02-08 15:31:19

标签: algorithm math numeric discrete-mathematics

鉴于i = 0到N-1的数字序列a [i],我试图计算以下总和:

a[0] * a[1] * a[2] +
a[1] * a[2] * a[3] +
a[2] * a[3] * a[4] +
...
a[N-4] * a[N-3] * a[N-2] +
a[N-3] * a[N-2] * a[N-1]

我想将一个相乘的组(在上面的示例中为3)的大小G设为可变参数。然后,可以使用简单的O(N * G)算法幼稚地获得结果,该算法可以用伪代码编写,如下所示:

sum = 0
for i from 0 to (N-G-1):
  group_contribution = 1
  for j from 0 to (G-1):
    group_contribution *= a[i+j]
  sum += group_contribution

但是,对于大G来说,显然该算法效率极低,尤其是假设序列a [i]的数量事先未知,并且必须在运行时进行昂贵的计算。

基于这个原因,我考虑使用以下复杂度为O(N + G)的算法,该算法通过计算滚动乘积来循环序列a [i]的值:

sum = 0
rolling_product = 1
for i from 0 to (G-1):
  rolling_product *= a[i]
sum += rolling_product
for i from G to (N-1):
  rolling_product /= a[i-G]
  rolling_product *= a[i]
  sum += rolling_product

但是我担心标准浮点表示中除法的数值稳定性。

我想知道是否存在一种稳定,更快的方法来计算此和。对我来说,这似乎是一项基本的数字任务,但现在我却茫然地如何高效地完成任务。

谢谢您的任何想法!

4 个答案:

答案 0 :(得分:7)

是的,如果您仔细计算反向偏乘积,则无需除法。

def window_products(seq, g):
    lst = list(seq)
    reverse_products = lst[:]
    for i in range(len(lst) - 2, -1, -1):
        if i % g != len(lst) % g:
            reverse_products[i] *= reverse_products[i + 1]
    product = 1
    for i in range(len(lst) - g + 1):
        yield reverse_products[i] * product
        if i % g == len(lst) % g:
            product = 1
        else:
            product *= lst[i + g]


print(list(window_products(range(10), 1)))
print(list(window_products(range(10), 2)))
print(list(window_products(range(10), 3)))

答案 1 :(得分:3)

作为序言,您可以考虑在两种算法上运行一些测试用例并比较结果(例如,相对误差)。

接下来,如果您有额外的内存和时间,这是一种稳定的方法,可以节省O( N log 2 G )时间和记忆。它类似于在range minimum query问题的恒定时间线性空间中的方法。

二次计算乘方范围的乘积

B [ i ] [ j ]为2 j 的乘积 a 的sup>元素从位置 i 开始,所以

B [ i ] [ j ] = a [ i ] × a [ i + 1]×...× a [ i + 2 j -1]

我们对 B 中的 N log 2 G 值感兴趣,即0≤ j ≤log 2 G 。我们可以在O(1)中计算每个值,因为

B [ i ] [ j ] = B [ i ] [ j -1]× B [ i + 2 j -1 ]] [ j -1]

计算总和

要计算总和中的一项,我们将 G 分解为2的幂次方块。例如,如果G = 13,则第一项是

a [0]×...× a [12] =( a [0]×...× a [7])×( a [8]×...× a [11])× a [ 12] = B [0] [3]× B [8] [2]× B [12] [0]

每个O( N )项都可以在O(log 2 G )时间内计算,因此查找的总复杂度总和为O( N log 2 G )。

答案 2 :(得分:0)

您的滚动产品是个好主意,但正如您所说,它在稳定性方面存在问题。我可以这样解决:

  • 分别使用类似的系统来跟踪零和负数的数量。这些是整数和,因此没有稳定性问题。
  • 而不是计算所有a[i]的滚动积,而是计算log(abs(a[i]))的滚动 sum (不包括零)。然后,当您需要该产品时,它就是(num_zeros > 0 ? 0.0 : exp(log_sum)) * sign。这样可以解决主要的不稳定因素。
  • 当您通过巧妙的滚动log_sum算法生成输出时,您应该同时构建一个 fresh log_sum,从中不减去任何东西。当该新总和中的元素数达到G时,请用该数字覆盖滚动的llog_sum并将其重置为零。这样可以消除长期累积的舍入误差。

答案 3 :(得分:0)

创建一个新序列b,其中b [0] = a [0] * a [1] * a [2] * ... a [G-1]等。

现在,您遇到了一个更简单的问题,即可以计算b值的总和,并且可以保持总计,每次添加b值都减去b [0]并添加新值并将它们全部向下滑动一个(使用循环缓冲,因此没有任何动作)。 [典型的滑动窗移动平均类型代码]

保留最后G a []个值的缓存并计算要添加到末尾的新值仅是O(G)运算,而您只计算一次a [i]。