斐波那契是数学中最值得讨论的一个问题,从12世纪斐波那契提出这个数列后,就有很多数学家研究过这个数列,对斐波那契数列的新发现也越来越多,这些细节我没能力去研究,这篇文章中要讲的是编程中对生成斐波那契数算法的优化。首先要说的就是斐波那契数列的定义,这一切都起源于一个生殖能力超强的兔子:
- 第一个月初有一对刚诞生的兔子
- 第二个月后(第三个月初)他们可以生育
- 每月没对兔子可生育的兔子会诞生下一对新兔子
- 兔子永不死去
几乎每个学计算机的在学编程语言的时候都会遇到这样的习题:计算第N个月兔子的总数
点击这里查看完整源代码,建议对着完整的代码调试。
最简单的递归算法
老师肯定会教的一种方法:
uint64_t fibonacci(unsigned int n) {
if (n == 0) return 0;
if (n <= 2) return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
该方法来自于斐波那契数列的一个递推式:
fib(n) = fib(n-1) + fib(n-2)
然后使用递归算法并指定递归出口,即可得出结果。
使用循环迭代消除递归
递归因为要不断地调用函数自身,调用函数就伴随着参数以及函数局部变量入栈,当递归层数较大容易产生栈溢出,所以通常需要我们使用循环优化递归算法。幸运地是,大多数递归都能修改成循环(使用自定义栈保存变量的方式仍然算递归)。而且上面的算法在效率上存在很大的优化空间:
你会发现fib(5) = fib(4) + fib(3),而求fib(4)的时候我们已经求过fib(3),这意味着我们做了很多重复的工作,很明显我们需要把前面做过的工作暂存。
递归算法时间呈指数形式增长:O(2^N);而使用循环迭代时间上呈线性增长:O(N)。在我笔记本上测试时,当n超过40递归算法的时间就开始爆炸了。
uint64_t fibonacci(unsigned int n) {
if (n == 0) return 0;
if (n == 1 || n == 2) return 1;
uint64_t f1 = 1, f2 = 1, fn;
for (unsigned int i = 3; i <= n; i++) {
fn = f1 + f2;
f1 = f2;
f2 = fn;
}
return fn;
}
矩阵算法求解
斐波那契数列的递推公式是:fib(n) = fib(n-1) + fib(n-2);我们可以用矩阵来表示这种关系:
进一步推到可以得到:
从0开始算得到Fn则需要更进一步:
我们要实现一下矩阵运算: