如何将递归代码更改为迭代形式

时间:2015-01-15 16:24:21

标签: c++ algorithm recursion iteration fft

我在下面实现了一个简单的FFT(忽略了最终的缩放):

typedef complex<double> base;
vector<base> w;
int FFTN = 1024;
void fft(vector<base> &fa){
    int n = fa.size();
    if (n==1) return;
    int half = (n>>1);
    vector<base> odd(half),even(half);
    for(int i=0,j = 0;i<n;i+=2,j++) {
        even[j] = fa[i];
        odd[j] = fa[i+1];    
    }    
    fft(odd);
    fft(even);    
    int fact = FFTN/n;    
    for (int i=0;i<half;i++){        
        fa[i] = even[i] + odd[i] * w[i * fact];
        fa[i + half] = even[i] - odd[i] * w[i * fact];
    }
}

效果很好。但我坚持将其转换为迭代形式。到目前为止我尝试了什么:

int n = fa.size();
int fact = (FFTN>>1);
int half = 1;
while(half<n){
    for(int i=0;i<n/half;i+=2){
        base even = fa[i], odd = fa[i+1];
        fa[i] = even + odd * w[i*fact];
        fa[i+half] = even - odd*w[i*fact];                            
    }
    for(int j=0;j<n/half;j++) 
        fa[j] = fa[j+half];
    fact >>= 1;
    half <<= 1;
}

有人可以帮我解决转换问题吗?

3 个答案:

答案 0 :(得分:1)

这是我的实施:

typedef complex<double> Data;

const double PI = acos(-1);

// Merges [low, (low + high) / 2) with [(low + high) / 2, high) parts.
void merge(vector<Data>& b, int low, int high) {
    int n = high - low;
    Data cur(1), mul(cos(2. * PI / n), sin(2. * PI / n));
    for (int i = low; i < low + n / 2; i++) {
        Data temp = b[i + n / 2] * cur;
        b[i + n / 2] = b[i] - temp;
        b[i] = b[i] + temp;
        cur = cur * mul;
    }
}

// Computes FFT for the vector b.
void do_fft(vector<Data>& b) {
    int n = b.size();
    int hi = 0;
    while ((1 << hi) < n)
        hi++;
    hi--;
    // Permutes the input vector in a specific way.
    vector<int> p(n);
    for (int i = 0; i < n; i++) 
        for (int b = hi; b >= 0; b--)
            if (i & (1 << b))
                p[i] |= (1 << (hi - b));
    vector<Data> buf(n);
    for (int i = 0; i < n; i++)
        buf[i] = b[p[i]];
    copy(buf.begin(), buf.end(), b.begin());
    for (int h = 2; h <= n; h *= 2)
        for (int i = 0; i < n; i += h)
            merge(b, i, i + h);
}

这种实现的想法是以这样一种方式置换给定的向量,即我们需要在每一步合并相邻的子向量(即[0,0]与[1,1],[2,2]在第一步用[3,3]等,[0,1]用[2,3],[4,5]和[6,7]在第二步,等等)。事实证明,元素应该以下列方式进行置换:我们应该采用元素索引的二元表示,反转它,并将具有反向索引的元素放到当前位置。我无法严格证明这一点,但为n = 8n = 16绘制小图片有助于理解它是正确的。

答案 1 :(得分:1)

这并没有完全提供解决方案。但是可能有助于解决类似问题的人将递归算法转换为迭代算法。递归在具有堆栈的系统中实现。每次对方法的递归调用都会将信息推送到堆栈上:

  1. 功能参数
  2. 本地变量
  3. 寄回地址
  4. 如果程序员可以使用stack + while loop执行上述操作,我们可以将迭代算法实现为递归算法。步骤将是

    1. 用于调用递归的参数 呼叫现在将被推送到堆栈。
    2. 然后我们进入while循环(直到堆栈为空),同时弹出 来自堆栈的参数(LIFO)和调用核心逻辑
    3. 继续将更多参数推送到堆栈并重复(2)直到堆栈为空。
    4. 使用上述方法进行迭代因子计算的代码示例。

      int coreLogic( int current, int recursiveParameter ) {
          return current * recursiveParameter ;
      }
      
      int factorial( int n ) {
      
          std::stack<int> parameterStack ;
      
          int tempFactorial = 1;
          //parameters that would have been used to invoke the recursive call will now be pushed to stack
          parameterStack.push( n );
      
          while( !parameterStack.empty() ) {
              //popping arguments from stack 
              int current = parameterStack.top();
              parameterStack.pop();
      
              //and invoking core logic
              tempFactorial = coreLogic( tempFactorial, current );
      
              if( current > 1 ) {
                  //parameters that would have been used to invoke the recursive call will now be pushed to stack
                  parameterStack.push(  current - 1 );
              }
      
              /*
              *if a divide and conquer algorithm like quick sort then again push right side args to stack 
              * - appers case in question
              *if( condition ) {
              *   parameterStack.push( args  );
              *}
              */
          }
          return tempFactorial;
      }
      

答案 2 :(得分:1)

我要做的第一件事就是让你的功能更加递归&#34;。

void fft(base* fa, size_t stride, size_t n) {
  if (n==1) return;
  int half = (n>>1);
  fft(fa+stride, stride*2, half); // odd
  fft(fa, stride*2, half); // even
  int fact = FFTN/n;  
  for (int i=0;i<half;i++){    
    fa[i] = fa[stride*2*i] + fa[stride*2+i+stride] * w[i * fact];
    fa[i + half] = fa[stride*2*i] - fa[stride*2+i+stride] * w[i * fact];
  }
}
void fft(std::vector<base>& fa){ fft(fa.data(), 1, fa.size()); }

现在我们在缓冲区内就地fft

由于我们现在有两个不同的fft实现,我们可以相互测试它们。此时构建一些单元测试,因此可以针对已知的&#34; good&#34;进行进一步的更改。 (或至少稳定的)一组行为。

接下来,我们可以检查元素在原始向量中组合的顺序。检查长度为4的缓冲区。

a   b   c   d

我们递言做奇数甚至

a[e]  b[o]  a[e]  d[o]

然后递归做奇数甚至

a[ee]  b[oe]  a[eo]   d[oo]

这些集合的大小为1.它们是单独存在的,然后我们将它们组合在奇数和偶数上。

现在我们看一下8.在两次递归之后,这些元素已经拥有了#39;由:

0[ee]   1[oe]   2[eo]   3[oo]   4[ee]   5[oe]   6[eo]  7[oo]

之后3:

0[eee]   1[oee]   2[eoe]   3[ooe]   4[eeo]   5[oeo]   6[eoo]  7[ooo]

如果我们撤消这些标签,并致电e 0o 1,我们会得到:

0[000]   1[001]   2[010]   3[011]   4[100]   5[101]   6[110]   7[111]

是二进制计数。第一位被丢弃,现在相等的元素在第二次到最后一次递归调用中合并。

然后丢弃前两位,并组合匹配最后一位的元素。

我们可以不用查看位,而是查看每个组合的开头和步幅。

第一个组合的步长等于数组长度(每个元素1个)。

第二个是长度/ 2。第三个是长度/ 4。

这一直持续到步幅1。

要组合的子阵列数等于步幅长度。

所以

for(size_t stride = n; stride = stride/2; stride!=0) {
  for (size_t offset = 0; offset != stride; ++offset) {
    fft_process( array+offset, stride, n/stride );
  }
}

其中fft_process基于:

  int fact = FFTN/n;  
  for (int i=0;i<half;i++){    
    fa[i] = fa[stride*2*i] + fa[stride*2+i+stride] * w[i * fact];
    fa[i + half] = fa[stride*2*i] - fa[stride*2+i+stride] * w[i * fact];
  }

可能是这样的:

void fft_process( base* fa, size_t stride, size_t n ) {
  int fact = FFTN/n; // equals stride I think!  Assuming outermost n is 1024.
  for (int i=0;i<half;i++){    
    fa[i] = fa[stride*2*i] + fa[stride*2+i+stride] * w[i * fact];
    fa[i + half] = fa[stride*2*i] - fa[stride*2+i+stride] * w[i * fact];
  }
}

这些都没有经过测试,但它提供了如何执行此操作的分步示例。您将要在此迭代版本上释放之前编写的单元测试(以测试fft的两个早期版本)。