递归Finbonacci优化
Recursive Finbonacci Optimization
下面的代码是用来计算前70个斐波那契数的。我有两个问题:
1) 为什么对于i
的每个连续值,程序变得越来越慢?是因为用高数字调用函数会导致占用大量内存吗。
2) 在运行时,我可以使用什么技术或编码方案来加快程序的计算速度?
#include <iostream>
int fib(int n) {
if (n == 1 || n == 2) return 1;
return fib(n - 1) + fib(n - 2);
}
void main() {
for (int i = 1; i<70; i++)
cout << " fib(" << i << ") = " << fib(i) << endl;
}
1) 为什么对于
i
的每个连续值,程序变得越来越慢?
只是函数的递归调用越多,执行时间就越长。
是因为用高数字调用函数会导致占用大量内存吗。
不,没有过多的内存占用(通过昂贵的动态内存分配操作)。所有需要的内存都保存在堆栈上,堆栈已经为进程预先分配。
不过,对于稍大的数字,您可能很容易耗尽可用的堆栈内存。
2) 在运行时,我可以使用什么技术或编码方案来加快程序的计算速度?
递归可能不是解决这个问题的最佳方法。这里已经有了更详细的答案:
有没有比这更好的方法(性能)来计算斐波那契?
除其他答案外。它变得越来越慢,因为程序需要"记住"计算结果。
如果必须使用递归,我建议您研究尾部递归。它重复使用上一个堆栈帧。参见C++中的尾递归
这里有一个小例子:
#include <iostream>
int tail_fib(int n, int a, int b) {
if (n == 0) return a;
if (n == 1) return b;
return tail_fib(n - 1, b, a + b);
}
void main() {
for (int i = 1; i < 45; i++)
cout << " fib(" << i << ") = " << tail_fib(i, 0, 1) << endl;
}
递归解决方案的主要问题是它具有O(2^N)复杂性。例如,要计算fib(10),必须计算fib)(9)+fib(8)。要计算fib(9),必须计算f(8)[第二次!]+f(7)。要计算f(8)[对于第一个和],必须。。。
最优解是使用具有O(N)复杂度的简单环路
unsigned int f(unsigned int n)
{
unsigned int retVal = 1;
if (n > 2)
{
unsigned int a, b = 1, c = 1;
for (unsigned int index = 2; index < n; index++)
a = b, b = c, c = a + b;
retVal = c;
}
return retVal;
}
[巨大的]差异是由于没有重新计算元素。
您可以通过交换内存来进行运行时优化——分配一个静态向量,每次调用该函数时,要么存储以前未计算的值,要么使用那些已经存储的值。
这将使程序运行时使用的最大N的内存和运行时计算为O(N)。
为了加快程序的速度,你需要使用记忆技术,这是一种非常奇特的说法,"不要重新计算,只需存储答案,并在需要时再次使用"。
您使用递归来计算答案,在每一步中,您都会调用之前已经计算过的函数,从而增加复杂性。上述程序的复杂度是指数级的,但您可以将其简化为线性时间。
您的代码经过一些小的编辑和memoisation
:
#include<iostream>
#define NOT_DEFINED -1
using namespace std;
long long memo[1000];
long long fib(int n){
if(memo[n] != NOT_DEFINED) return memo[n];
if(n==1 || n==2) return 1;
return memo[n] = fib(n-1)+fib(n-2);
}
int main(){
for(int i = 0;i < 1000;i++) memo[i] = NOT_DEFINED;
for(int i=1; i<70; i++)
cout<<" fib("<<i<<") = "<<fib(i)<<endl;
return 0;
}
链接到ideone上的解决方案:http://ideone.com/jW1VKD
递归斐波那契计算具有指数复杂性,因此速度非常慢。你可以在这里阅读分析
要想更快地计算斐波那契,甚至不要考虑使用递归只使用普通循环,如下所示:
#include <iostream>
#include <cassert>
#include <stdint.h>
uint64_t fib(uint64_t n)
{
assert(n>0);
uint64_t fprev = 1;
uint64_t fprev2 = 0;
while (--n>0)
{
uint64_t t = fprev2;
fprev2 = fprev;
fprev += t;
}
return fprev;
};
int main(int, char**)
{
for(uint64_t i=1; i<70; i++)
std::cout<<" fib("<<i<<") = "<<fib(i)<<std::endl;
return 0;
}
如果您需要更快地计算,您可以在程序启动时预先计算它们,并保存到表中以供进一步使用。
此外,斐波那契序列增长非常快(也像指数函数),所以要注意整数溢出。
我想定量强调递归算法的一个非常烦人的特性。
当您调用fib(1)
或fib(2)
时,函数会立即返回,因此在这两种情况下执行的调用次数都恰好是1
。我们编写c(1) = c(2) = 1
,其中c(n)
是为计算fib(n)
而执行的调用数。
当您用n > 2
调用fib(n)
时,您会间接调用fib(n-1)
和fib(n-2)
,调用总数为1 + c(n-1) + c(n-2)
。
因此c(n)
是由递归定义的
c(n) = c(n-1) + c(n-2) + 1,
c(1) = c(2) = 1
这个解叫做列奥纳多数(https://oeis.org/A001595)。
给定递归的形式,您很容易看到这些数字超过了Fibonacci数,因此它需要比数字本身的值更多的递归函数调用来计算第N个Fibonacci。随着斐波那契数呈指数级增长,调用次数也呈指数级增加。
因此,这不仅仅是"更多的递归调用",它是"大量的更递归的调用"。这使得该方法对于大型CCD_ 16来说效率极低且不切实际。
幸运的是,有一种简单的方法可以通过应用递推(在其他答案中给出)来迭代计算数字。
同样令人感兴趣的是直接公式
Fn = φ^n/√5
四舍五入到最接近的整数,其中φ
是黄金比率。
这里有一个简单的递归版本,它在线性时间O(N)中运行。诀窍是返回两个值而不是一个。
void Fibo(int N, int& F0, int& F1)
{
if (N == 1) {
F1= F0= 1;
}
else {
Fibo(N - 1, F0, F1);
F1= F1 + F0;
F0= F1 - F0;
}
}
- 空基优化子对象的地址
- 关闭||运算符优化
- 如何解决gcc编译器优化导致的centos双编译器设置中的分段错误
- 返回值优化:显式移动还是隐式
- 人脸跟踪arduino代码的优化
- 使用仅使用一次的变量调用的复制构造函数.这可能是通过调用move构造函数进行编译器优化的情况吗
- 纯函数,为什么没有优化
- 为什么大多数 pair 实现默认不使用压缩(空基优化)?
- 如何以优化的方式同时迭代两个间距不相等的数组
- 小字符串优化(调试与发布模式)
- 浮点定向舍入和优化
- Visual Studio 调试优化如何工作?
- 为什么开关的优化方式与 c/c++ 中的链接不同?
- 线性优化目标函数中的绝对值
- GCC 会优化内联访问器吗?
- gcc 如何优化此循环?
- 如何防止 CUDA-GDB 中的<优化输出>值
- 为什么我的程序在 O0 和 O2 的优化级别返回不同的结果
- 这个C++编译器优化(在自身的实例上调用对象自己的构造函数)的名称是什么,它是如何工作的?
- 递归Finbonacci优化