问题起源
兔子生长的数目问题:
第一个月初有一对刚诞生的兔子
第二个月之后(第三个月初)它们可以生育
每月每对可生育的兔子会诞生下一对新兔子
兔子永不死去
如果笔算, 前几项可以很快得出:
n n n | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
F n F_n Fn | 0 | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 | 34 |
数学描述
递推公式
F 0 = 0 F 1 = 1 F n = F n − 1 + F n − 2 \begin{aligned} F_0 &= 0 \\ F_1 &= 1 \\ F_n &= F_{n-1} + F_{n-2} \\ \end{aligned} F0F1Fn=0=1=Fn−1+Fn−2通项公式
F n = 1 5 [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] , n ≥ 0 F_n = \dfrac{1}{\sqrt{5}} [(\dfrac{1 + \sqrt{5}}{2})^{n} – (\dfrac{1 – \sqrt{5}}{2})^{n}], n \ge 0 Fn=5 1[(21+5 )n−(21−5 )n],n≥0通项公式推导
参见: 利用特征方程
代码实现
注意:当 n < 0 时, 对应现实中的兔子问题,返回0.
代码实现有递归、迭代、利用通项等方式, 分别给出:
直接递归(根据定义)
int fib_recursive(int n) { if (n < 0) { return 0; } if (n < 2) { return n; } return fib_recursive(n - 1) + fib_recursive(n - 2); }
直接递归,当n太大时会造成栈溢出
尾递归
关于尾递归可以参看: 尾调用优化int fib_recursive_tail(int n, int a, int b) { if (n < 0) { return 0; } if (n == 0) { return a; } if (n == 1) { return b; } return fib_recursive_tail(n - 1, b, a + b); }
借用数组迭代
arr[i] = arr[i – 1] + arr[i – 2]int fib_iteration_arr(int n) { if (n < 0) { return 0; } if (n < 2) { return n; } int* arr = (int*)malloc((n + 1) * sizeof(int)); arr[0] = 0; arr[1] = 1; int i; int ret = 0; for (i = 2; i <= n; i++) { arr[i] = arr[i - 1] + arr[i - 2]; } ret = arr[n]; free(arr); return ret; }
利用数组迭代, 需要额外的内存空间(如上述代码中的malloc向堆中申请了内存);
事实上, 我们需要的只是a[n], 所以可以考虑临时变量来实现;借用临时变量迭代
int fib_iteration(int n) { if (n < 0) { return 0; } if (n < 2) { return n; } int a = 0, b = 1; int t; while (n--) { t = b; b = a + b; a = t; } return a; }
利用通项公式
#include <math.h> int fib_formula(int n) { if (n < 0) { return 0; } const double sqrt_5 = sqrt(5); return round( (pow((1 + sqrt_5) / 2, n) - pow((1 - sqrt_5) / 2, n)) / sqrt_5 ); }
利用通项公式时,要利用几个库函数(sqrt, pow, round)。缓存了 5 \sqrt{5} 5 的值, 避免多次计算。
参考
- https://zh.wikipedia.org/wiki/斐波那契数列
- https://baike.baidu.com/item/特征方程/2678218
- http://www.ruanyifeng.com/blog/2015/04/tail-call.html
欢迎补充指正!