在C中将递归函数转换为迭代函数

时间:2018-05-24 15:26:23

标签: c function math recursion iteration

我需要一个迭代函数,但我只能将它视为递归

int f(int m,int n)
{
    if (n == 0)
        return m;
    if (m == 0)
        return n;
    return f(m+1, n-1) + f(m, n-1) + f(m-1, n-1) + f(m-1, n);
}

问题是第三种情况,因为我说我只能递归地思考它。我正在使用C。

3 个答案:

答案 0 :(得分:0)

你可以使用一个堆栈来保持一对整数(m,n),每次你从堆栈中弹出一个元素时,你会推回以下内容。如果m或n为零,那么不要推回以下,您将m或n(取决于零)取决于您的运行总和。

(m+1, n-1) 
(m, n-1) 
(m-1, n-1) 
(m-1, n)

一旦你的筹码是空的,你的总和将是答案。尽管这相当于您的递归方法,但您最好使用动态编程。

答案 1 :(得分:0)

为了提供有关Nico Schertler的想法的更多细节,您实际需要的是按对角线顺序计算值(因为公式中有m+1)。为了更清楚,让我们引入另一个函数g,使其

g(k, n) = f(k-n, n)

或者换句话说

f(m, n) = g(m+n, n)

现在让我们用f重写g的递归公式:

g(m+n, n) = g(m+n, n-1) + g(m+n-1, n-1) + g(m+n-2, n-1) + g(m+n-1, n)

g(k, n) = g(k, n-1) + g(k-1, n-1) + g(k-2,  n-1) + g(k-1, n)

现在您可以看到,此处kn从未增长,因此如果您只拥有前两行的所有值,即g(k,n),则可以计算g(k-1, i)g(k-2, i) +当前行中的先前值。

如果你把它放到代码中,你可能会得到这样的东西:

#include <stdio.h>

int f(int m,int n)
{
    if (n == 0)
        return m;
    if (m == 0)
        return n;
    return f(m+1, n-1) + f(m, n-1) + f(m-1, n-1) + f(m-1, n);
}

int f2(int m, int n) {
    // covers bad cases such as m = n = 0
    if (m == 0)
        return n;
    if (n == 0)
        return m;
    int buf1[m + n + 1];
    int buf2[m + n + 1];
    int buf3[m + n + 1];
    int* curLine = buf1;
    int* prevLine1 = buf2;
    int* prevLine2 = buf3;
    // init first two lines with f(0,0), f(1,0) and f(0,1)
    prevLine1[0] = 0; 
    curLine[0] = 1;  
    curLine[1] = 1;
    for (int i = 2; i <= m + n; i++) {
        // cycle buffers to avoid dynamic allocation for each line
        // curLine -> prevLine1 -> prevLine2 -> curLine
        int* tmp = prevLine2;
        prevLine2 = prevLine1;
        prevLine1 = curLine;
        curLine = tmp;
        curLine[0] = curLine[i] = i;  // f(0,i) and f(i,0)
        for (int j = 1; j < i; j++) {
            curLine[j] = curLine[j - 1]  // m+1, n-1
                    + prevLine1[j] //m-1, n
                    + prevLine1[j - 1] //m, n-1
                    + prevLine2[j - 1]; //m-1, n-1
        }
    }
    return curLine[n];
}

int main()
{
    for(int n = 0; n < 6; n++)
       for(int m = 0; m < 6; m++) {
           int fv = f(m,n);
           int f2v = f2(m,n);
           printf("n = %d, m = %d, f = %d f2 = %d\n", n, m, fv, f2v);
       }

    return 0;
}

请参阅online demo,其中显示ff2的ouptut是相同的。

更新(更简单的代码)

实际上你不需要对角线。是的,依赖m+1,但n依赖仅适用于nn-1。因此,通过n(而不是m)创建外部循环就足够了,只需进入内部循环到m+n-i(而不是直到m)然后你只需要前一行f的值。代码就像这样

int f3(int m, int n){
    // covers bad cases such as m = n = 0
    if (m == 0)
        return n;
    if (n == 0)
        return m;
    int buf1[m + n + 1];
    int buf2[m + n + 1];
    int* curLine = buf1;
    int* prevLine = buf2;

    // f(i, 0)
    for (int i = 0; i <= m + n; i++)
        curLine[i] = i;

    for (int i = 1; i <= n; i++) {
        // swap buffers to avoid dynamic allocation for each line
        int* tmp = prevLine;
        prevLine = curLine;
        curLine = tmp;
        curLine[0] = i; // f(0, i)
        for (int j = 1; j <= n + m - i; j++) {
            curLine[j] = curLine[j - 1] //m-1, n
                    + prevLine[j + 1]   //m+1, n-1
                    + prevLine[j]       //m, n-1
                    + prevLine[j - 1];  //m-1, n-1

        }
    }

    return curLine[m];
}

f2f3的更新演示版为here

答案 2 :(得分:0)

使用其他人建议的动态编程。

首先,在堆栈中创建一个足够大的矩阵,它将存储将被重用的中间值,例如:

int matrix[20][20];

然后,考虑计算一般解决方案所需的值。例如,如果您被请求f(4, 2),则将访问的值为:

  0 1 2 3 4 5 6
0 # # # # # # #
1 # # # # # #
2 # # # # #

之后,考虑如何以尽可能最快的方式填充矩阵的所有值,最后在(4, 2)的请求解决方案中。

这是您可以期待的表现:

--------------------------------------------------------
Benchmark                  Time           CPU Iterations
--------------------------------------------------------
f_original/1/1             9 ns          9 ns   72315573
f_original/8/1            69 ns         69 ns   10191593
f_original/1/8        337200 ns     337086 ns       1840
f_original/8/8     272417747 ns  272221086 ns          2
f_stack/1/1                9 ns          9 ns   80751042
f_stack/8/1               51 ns         50 ns   12440282
f_stack/1/8           243803 ns     242299 ns       2779
f_stack/8/8        177525517 ns  177187353 ns          5
f_memoization/1/1        229 ns        229 ns    3681680
f_memoization/8/1        255 ns        254 ns    2457598
f_memoization/1/8        517 ns        517 ns    1116021
f_memoization/8/8       1161 ns       1159 ns     554004
f_dynamic/1/1              7 ns          7 ns   90095265
f_dynamic/8/1             16 ns         16 ns   41905352
f_dynamic/1/8             52 ns         52 ns   11358252
f_dynamic/8/8            128 ns        127 ns    5244860

说明:

  • f_original:您发布的功能。
  • f_stack:@basak建议的方法。这是问题的有效答案,因为它不使用递归(即您提供自己的堆栈而不是使用函数调用提供的堆栈)。
  • f_memoization:与原版相同,但有记忆。这不是一个有效的答案,因为它仍然是递归的。
  • f_dynamic:动态编程方法。

正如您所看到的,大多数收益都来自于memoization,但是通过矩阵运行而不是使用递归调用会使它快一个数量级。

之后,您可以进一步优化。例如,如果您不打算重用中间值,则可以避免保留所有矩阵 - 而是保留最后两行,如@SergGr所指出的那样:

f_optimized/1/1            8 ns          8 ns   76327765
f_optimized/8/1           16 ns         15 ns   42463467
f_optimized/1/8           44 ns         43 ns   12702290
f_optimized/8/8           90 ns         89 ns    7176011

更重要的是,如果您的int足够小(例如32位),那么您只需预先计算大部分值 - 399只有m >= 3个值适合签名的32位整数,所以你可以保留它们。这大约是1.5 KiB个数据,然后您有O(1)函数返回2 ns