计算前缀总和

时间:2010-10-24 18:05:16

标签: c++ algorithm

我有以下代码来完成前缀和任务:

  #include <iostream>
  #include<math.h>
  using namespace std;

  int Log(int n){
      int count=1;
      while (n!=0){
          n>>=1;
          count++;

      }
      return count;
  }
  int main(){
    int x[16]={39,21,20,50,13,18,2,33,49,39,47,15,30,47,24,1};
    int n=sizeof(x)/sizeof(int );
    for (int i=0;i<=(Log(n)-1);i++){
          for (int j=0;j<=n-1;j++){
              if (j>=(std::powf(2,i))){
                  int t=powf(2,i);
                  x[j]=x[j]+x[j-t];

              }
          }
     }
     for (int i=0;i<n;i++)
          cout<<x[i]<< "  ";

     return 0;
  } 

来自this wikipedia page 但我错了结果有什么不对?请帮忙

3 个答案:

答案 0 :(得分:6)

我不确定你的代码应该做什么,但实现前缀和实际上非常简单。例如,它使用任意操作计算迭代器范围的(独占)前缀和:

template <typename It, typename F, typename T>
inline void prescan(It front, It back, F op, T const& id) {
    if (front == back) return;
    typename iterator_traits<It>::value_type accu = *front;
    *front++ = id;
    for (; front != back; ++front) {
        swap(*front, accu);
        accu = op(accu, *front);
    }
}

这遵循C ++标准库算法的界面风格。

要从代码中使用它,您可以编写以下内容:

prescan(x, x + n, std::plus<int>());

您是否尝试实施并行前缀和?这只有在您实际并行化代码时才有意义。按照目前的情况,你的代码是按顺序执行的,并且不会从你似乎采用的更复杂的鸿沟和征服逻辑中获得任何东西。

此外,您的代码确实存在错误。最重要的一个:

for(int i=0;i<=(Log(n)-1);i++)

在这里,你要迭代到floor(log(n)) - 1。但是伪代码表明你实际上需要迭代到ceil(log(n)) - 1

此外,请考虑一下:

 for (int j=0;j<=n-1;j++)

这不常见。通常,您可以编写如下代码:

for (int j = 0; j < n; ++j)

请注意,我使用<代替<=,并将界限从j - 1调整为j。对于外循环也是如此,所以你得到:

for (int i = 0; i < std::log(n); ++i)

最后,您可以使用std::powfpow(2, x)相同的事实代替1 << x(即利用操作库2作为位操作有效实现的事实)。这意味着你可以简单地写:

if (j >= 1 << i)
    x[j] += x[j - (1 << i)];

答案 1 :(得分:4)

我认为std :: partial_sum会做你想要的 http://www.sgi.com/tech/stl/partial_sum.html

答案 2 :(得分:3)

让算法正常工作的最快方法:删除外部for(i...)循环,而不是将i设置为0,并仅使用内部for (j...)循环。

int main(){
    ...
    int i=0;
    for (int j=0;j<=n-1;j++){
        if (j>=(powf(2,i))){
            int t=powf(2,i);
            x[j]=x[j]+x[j-t];
        }
    }
    ...
}

或等效地:

for (int j=0; j<=n-1; j++) {
    if (j>=1)
        x[j] = x[j] + x[j-1];
}

...这是执行前缀和的直观方式,也可能是最快的非并行算法。

维基百科的算法旨在并行运行,这样所有的添加都完全相互独立。它读取所有值,添加到它们中,然后将它们全部并行地写回到数组中。在你的版本中,当你执行x [j] = x [j] + x [j-t]时,你正在使用你刚刚添加的x [j-t],t迭代前。

如果你真的想要重现维基百科的算法,这是一种方法,但要注意它将比上面的直观方式慢得多,除非你使用的是并行编译器和带有一大堆处理器的计算机。

int main() {
    int x[16]={39,21,20,50,13,18,2,33,49,39,47,15,30,47,24,1};
    int y[16];
    int n=sizeof(x)/sizeof(int);

    for (int i=0;i<=(Log(n)-1);i++){
        for (int j=0;j<=n-1;j++){
            y[j] = x[j];
            if (j>=(powf(2,i))){
                int t=powf(2,i);
                y[j] += x[j-t];
            }
        }
        for (int j=0;j<=n-1;j++){
            x[j] = y[j];
        }
    }

    for (int i=0;i<n;i++)
        cout<<x[i]<< "  ";
    cout<<endl;
}

附注:为了提高速度,您可以使用1<<i代替powf(2,i)。正如ergosys所提到的,你的Log()功能需要工作;它返回的值太高,在这种情况下不会影响部分和的结果,但会花费更长的时间。