在递归算法中获得更快的代码
Get a faster code in recursion algorithm
我正在学习 C 中的递归。
我的问题是:打印 80 个第一个斐波那契数(使用递归)
这是本书的代码:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
long long f[100];
long long fib(int n)
{
if (n<2) return 1;
if (f[n]>0) return f[n];
f[n] = fib(n-1)+fib(n-2);
return f[n];
}
int main()
{
int i;
for (i = 0; i<= 80; i++) printf ("%lld \n", fib(i));
system ("pause");
return 0;
}
使用这段代码,我的程序运行得非常快,我立即得到了 80 个斐波那契数列。
但是,当我删除第 10 行时:
if (f[n] > 0) return f[n];
程序变得很慢,打印所有80个数字大约需要20秒。
谁能解释为什么?谢谢。
那是因为缓存的结果不再以这种方式使用。你看,当你使用递归函数时,例如 f(20) 被多次调用,试着在纸上画出调用树,你就会看到。该行所做的基本上是避免重新计算这些值(记忆,如果你想这样说的话)。
该算法正在缓存数组 f
中先前计算的值。
这个数组用 0 初始化(因为它是静态的)。考验你
提到删除检查是否有缓存值,并且 returns 它。经过
消除测试,你永远不会使用缓存的值,而是重新计算它
每次。这很昂贵,因为您最终要计算
相同的值很多很多次。
编辑:
我可能会补充一点,如果这是书中的代码,你应该得到一个不同的
书,因为画风很差。在 C 中,我会这样写:
long long
fib( int n )
{
static long long cache[100]; // limit scope, and give it a good name
assert( n >= 0 && n < sizeof(cache) / sizeof(cache[0]) );
// make sure input is legal.
if ( cache[n] == 0 ) {
cache[n] = n < 2 ? 1 : fib( n - 1 ) + fib( n - 2 );
}
return cache[n];
}
您会注意到,这样代码实际上要简单得多。
在 C++ 中,当然,我会使用 std::vector
作为缓存,所以我没有
一些隐藏的、硬编码的限制。 (就此而言,在 C 中,我可能会
实现类似的东西,以避免硬编码限制。但是我
不会期望在教学示例中。)
首先,如果您使用朴素的递归公式(即,如果您在代码中注释第 10 行)
F(n) = F(n-1) + F(n-2)
如您所见,它多次计算许多值(因此效率低下),或者换句话说,随着每个节点解析为 2 个分支(O(2^n)
)
,时间复杂度呈指数级增长
因此,如果他保存我们的工作并使用它来解决再次发生的相同问题:
我们可以实现线性时间复杂度 ( O(n)
)
在您的代码中,数组 f
是 cache
但是,如果您有兴趣知道(尽管没有被问及),您可以通过矩阵指数以对数时间复杂度计算 Fib(n)
或通常比这更快的任何线性递归关系。 ( O(logN)
)
这种解题思路叫做动态规划,这种特殊的方法叫做记忆:
http://en.wikipedia.org/wiki/Dynamic_programming
这个想法是存储以前计算的值并使用它们而不是再次计算它们。在您的情况下,它们存储在:f[100]
并且一旦计算出斐波那契数,就永远不会重新计算。当您删除分配时,您会禁用此存储并且每次都会重新计算值。
我正在学习 C 中的递归。 我的问题是:打印 80 个第一个斐波那契数(使用递归)
这是本书的代码:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
long long f[100];
long long fib(int n)
{
if (n<2) return 1;
if (f[n]>0) return f[n];
f[n] = fib(n-1)+fib(n-2);
return f[n];
}
int main()
{
int i;
for (i = 0; i<= 80; i++) printf ("%lld \n", fib(i));
system ("pause");
return 0;
}
使用这段代码,我的程序运行得非常快,我立即得到了 80 个斐波那契数列。
但是,当我删除第 10 行时:
if (f[n] > 0) return f[n];
程序变得很慢,打印所有80个数字大约需要20秒。 谁能解释为什么?谢谢。
那是因为缓存的结果不再以这种方式使用。你看,当你使用递归函数时,例如 f(20) 被多次调用,试着在纸上画出调用树,你就会看到。该行所做的基本上是避免重新计算这些值(记忆,如果你想这样说的话)。
该算法正在缓存数组 f
中先前计算的值。
这个数组用 0 初始化(因为它是静态的)。考验你
提到删除检查是否有缓存值,并且 returns 它。经过
消除测试,你永远不会使用缓存的值,而是重新计算它
每次。这很昂贵,因为您最终要计算
相同的值很多很多次。
编辑:
我可能会补充一点,如果这是书中的代码,你应该得到一个不同的 书,因为画风很差。在 C 中,我会这样写:
long long
fib( int n )
{
static long long cache[100]; // limit scope, and give it a good name
assert( n >= 0 && n < sizeof(cache) / sizeof(cache[0]) );
// make sure input is legal.
if ( cache[n] == 0 ) {
cache[n] = n < 2 ? 1 : fib( n - 1 ) + fib( n - 2 );
}
return cache[n];
}
您会注意到,这样代码实际上要简单得多。
在 C++ 中,当然,我会使用 std::vector
作为缓存,所以我没有
一些隐藏的、硬编码的限制。 (就此而言,在 C 中,我可能会
实现类似的东西,以避免硬编码限制。但是我
不会期望在教学示例中。)
首先,如果您使用朴素的递归公式(即,如果您在代码中注释第 10 行)
F(n) = F(n-1) + F(n-2)
如您所见,它多次计算许多值(因此效率低下),或者换句话说,随着每个节点解析为 2 个分支(O(2^n)
)
因此,如果他保存我们的工作并使用它来解决再次发生的相同问题:
我们可以实现线性时间复杂度 ( O(n)
)
在您的代码中,数组 f
是 cache
但是,如果您有兴趣知道(尽管没有被问及),您可以通过矩阵指数以对数时间复杂度计算 Fib(n)
或通常比这更快的任何线性递归关系。 ( O(logN)
)
这种解题思路叫做动态规划,这种特殊的方法叫做记忆:
http://en.wikipedia.org/wiki/Dynamic_programming
这个想法是存储以前计算的值并使用它们而不是再次计算它们。在您的情况下,它们存储在:f[100]
并且一旦计算出斐波那契数,就永远不会重新计算。当您删除分配时,您会禁用此存储并且每次都会重新计算值。