【问题标题】:Optimize a summing of array (subset problem)优化数组求和(子集问题)
【发布时间】:2011-08-26 01:41:40
【问题描述】:

在我的程序最热门的部分(根据 gprof,90% 的时间),我需要将一个数组 A 与另一个 B 相加。两个数组的大小均为 2^n(n 为 18..24),并保存一个整数(为简单起见,实际上存储的元素是 mpz_t 或小的 int 数组)。求和规则:对于0..2^n-1中的每个i,设置B[i] = sum (A[j]),其中j为位向量,j & ~ i == 0(即任意j的第k位都可以'如果i 的第 k 位不是 1,则不设置为 1)。

我当前的代码(这是最内层循环的主体)在 2^(1.5 * n) 个总和的时间内执行此操作,因为我将为每个 i 迭代(平均)2^(n/2) 个元素A.

  int A[1<<n]; // have some data
  int B[1<<n]; // empty
  for (int i = 0; i < (1<<n); i++ ) {
    /* Iterate over subsets */
    for (int j = i; ; j=(j-1) & i ) {
      B[i] += A[j];  /* it is an `sum`, actually it can be a mpz_add here */
      if(j==0) break;
    }
  }

我之前提到过,几乎所有的总和都是从前面求和的值中重新计算出来的。我建议,可以有代码,在n* 2^n sums 的时间内做同样的任务。

我的第一个想法是B[i] = B[i_without_the_most_significant_bit] + A[j_new];其中 j_new 只是 j 在“1”状态下具有来自 i 的 most_significant 位。这将我的时间减半,但这还不够(实际问题规模仍然需要数小时和数天):

  int A[1<<n];
  int B[1<<n];
  B[0] = A[0]; // the i==0 will not work with my idea and clz()
  for (int i = 1; i < (1<<n); i++ ) {
    int msb_of_i = 1<< ((sizeof(int)*8)-__builtin_clz(i)-1);
    int i_wo_msb = i & ~ msb;
    B[i] = B[i_wo_msb];
    /* Iterate over subsets */
    for (int j_new = i; ; j_new=(j_new-1) & i ) {
      B[i] += A[j_new];  
      if(j_new==msb) break; // stop, when we will try to unset msb
    }
  }

你能推荐更好的算法吗?

附加图像,i 和 j 的列表为每个 i 求和,n=4:

i  j`s summed
0  0
1  0 1
2  0 2
3  0 1 2 3
4  0 4
5  0 1 4 5
6  0 2 4 6
7  0 1 2 3 4 5 6 7
8  0                8
9  0 1              8 9
a  0 2              8 a
b  0 1 2 3          8 9 a b
c  0 4              8 c
d  0 1 4 5          8 9 c d
e  0 2 4 6          8 a c e
f  0 1 2 3 4 5 6 7  8 9 a b c d e f

注意图形的相似性

PS msb 魔法来自这里:Unset the most significant bit in a word (int32) [C]

【问题讨论】:

  • 我很想知道您在这里做什么 - 它可能有助于我们就优化策略提出建议。
  • 你需要所有 2^n 个子集吗?您可以通过将输入分成相等的两半 A 和 B,并枚举它们的子集,从而通过中间风格的方法求解子集总和。然后找到目标总和c,遍历A,对于每个元素a,在B中搜索c - a。
  • 我建议,优化后的代码将有从 n 到 0 的循环 k,其中可以计算 k 位前缀 i 和 j 的子和。

标签: c algorithm optimization subset


【解决方案1】:

分而治之?现在还没有到位。

void sums(int *a, int n, int *b) {
  if (n <= 0) {
    *b = *a;
    return;
  }
  int m = 1 << (n - 1);
  sums(a, n - 1, b);
  sums(a + m, n - 1, b + m);
  for (int i = 0; i < m; i++) {
    b[m + i] += b[i];
  }
}

【讨论】:

  • 注意:根据主定理,复杂度是线性的。
  • 为了提高速度,扩大基本情况并使用限制。
  • sped,ab 元素将添加多少?如果 j 位集是 i 的子集,检查在哪里?我应该如何调用这个函数?
  • @osgx 这是 O(n 2^n) 加法。检查是隐含在递归调用中的。 abn 对应于您的 ABn
  • 太棒了!很快!在速度快 2 倍的计算机上,这仅需 1-2 分钟,而解决方案需要数小时才能完成。欢迎来到 stackoverflow,这是一个很好的开始答案!
猜你喜欢
  • 2012-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-20
  • 2013-09-26
  • 2017-02-06
相关资源
最近更新 更多