我需要一个迭代函数,但我只能将它视为递归
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。
答案 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)
现在您可以看到,此处k
和n
从未增长,因此如果您只拥有前两行的所有值,即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,其中显示f
和f2
的ouptut是相同的。
更新(更简单的代码)
实际上你不需要对角线。是的,依赖m+1
,但n
依赖仅适用于n
和n-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];
}
f2
和f3
的更新演示版为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
。