斐波那契数列(Fibonacci sequence),又称黄金分割数列,当n趋向于无穷大时,前一项与后一项的比值越来越逼近黄金分割0.618(或者说后一项与前一项的比值小数部分越来越逼近 0.618)。
lim n → + ∞ a n a n + 1 = 5 − 1 2 \lim_{n \to +\infty} \frac{a_n}{a_{n+1}} \quad = \ \frac{\sqrt{5}-1}{2} n→+∞liman+1an= 25−1
黄金比例和斐波那契数列的数学意义密切相关,在我们的生活中小到细胞分裂、花瓣的排列纹路,大到人口密度和土地面积测绘,甚至是宇宙星系都有着斐波那契数列的身影。
有关斐波那契数列的数学定理和相关公式请参考斐波那契数列——百度词条。其中斐波那契数列的排列方式很有趣,前两项的和等于第三项的和,我们很容易就可以依据这个特点写出它的递推公式: a n = a n − 1 + a n − 2 a_n = a_{n-1} + a_{n-2} an=an−1+an−2 。并且按照递推公式我们还可以推导出相应的通项公式:
a n = 1 5 [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] (通项公式) a_n = \frac{1}{\sqrt{5}} \left[ \left( \frac{1+\sqrt{5}}{2} \right)^n - \left( \frac{1-\sqrt{5}}{2} \right)^n \right] \tag{通项公式} an=51[(21+5)n−(21−5)n](通项公式)
下面让我们通过几个示例来了解斐波那契数列的魅力把。
同样的,这次先用循环和递归做一遍,然后在想办法进行优化。
#include
using namespace std;
/*
题目:无穷数列 1,1,2,3,5,13,21,34,55,..., 称为 Fibonacci 数列,计算第n位数列。
*/
// 循环实现
int Fibonacci(int n)
{
/*
1 1 2 5
a=1 b=1
c=a+b=2
a=b b=c
分析:
c 保存两数之和,a、b向后移动
*/
int a = 1, b = 1, c = 1;
for (int i = 3; i <= n; ++i)
{
c = a + b;
a = b;
b = c;
}
return c;
}
// 递归实现
int Fib(int n)
{
if (n <= 2) return 1;
else return Fib(n - 1) + Fib(n-2);
}
int main()
{
for (int i = 1; i < 10; ++i)
{
cout << Fibonacci(i) << " ";
cout << Fib(i) << endl;
}
return 0;
}
分析递归效率:
对于循环来讲,它使用递推公式的方式求解,基本上没有需要优化的地方,除非我们选择使用通项公式求解,而对于当前的递归来说却还有着一些优化的空间。
递归过程分析:如:欲求的第五位斐波那契数,其递归调用方式如下
如图所示:
我们发现仅仅是计算第五个斐波那契数就要重复计算两次求3问题,三次求2问题,两次求1问题,这些重复的计算显然是没有意义的。从我们人的角度来分析问题,我们只需要计算一次“1”、一次“2”、一次“3”、一次“4”,就可以得到“5”的值了。并且我们也确实这样做了。正如我们所见到的,我们使用循环实现的求斐波那契数就是按照这种逻辑设计的。
前面提到过,通常情况下循环问题可以转换为递归问题,那么我们是否可以把循环式的代码稍加修改变成递归式呢?
循环式分析:
在循环中使用了三个变量,a、b 用于保存 a n − 1 a_{n-1} an−1、 a n − 2 a_{n-2} an−2 的值,使用 c 保存相加后的结果,a、b再向后移动一位。整体的过程如下表所示:
1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 | 34 |
---|---|---|---|---|---|---|---|---|
a | b | c | ||||||
a | b | c | ||||||
a | b | c | ||||||
a | b | c | ||||||
a | b | c | ||||||
a | b | c | ||||||
a | b | c |
递归算法设计:
对于一个 1, 1, 2, 3, 5, 8, 13, 21, 34
的数列,递归的求法是将大问题转化为小问题后在求解,如:求34问题->求21问题->求13问题->求5问题->求3问题->求2问题。
分析:把这个数列类比成一个数组的话,递归就像是倒着数组求值,循环是正向对数组求值。并且在整个过程中都是遍历完整个数组的,递归次数与循环次数相同。
算法:在进行每一步递归时同时进行计算,从斐波那契数的起始数 1,1 开始计算,待到递归到结束时已经求得斐波那契数值。并且这个算法不存在重复求值问题。
递归调用式: f i b ( n , a , b ) = > f i b ( n − 1 , b , a + b ) fib(n , a,b) \ => fib(n-1,b,a+b) fib(n,a,b) =>fib(n−1,b,a+b) ,调用过程中,n为递归次数,a 的位置保存b的值,b 的位置保存 c 的值(a+b)
递归过程分析:求第9个斐波那契数
fib(9,1,1) ⇒ fib(8,1,2) ⇒ fib(7,2,3) ⇒ fib(6,3,5) ⇒ fib(5,5,8) ⇒ fib(4,8,13) ⇒ fib(3,13,21) ⇒ fib(2,21,34)
a 1 = 1 , a 2 = 1 , a 3 = 2 , a 4 = 3 , a 5 = 5 , a 6 = 8 , a 7 = 13 , a 8 = 21 , a 9 = 34 \ a_1=1 \ \ , \ \ a_2=1 \ \ \ ,\ \ a_3 = 2 \ \ \ ,\ \ a_4 = 3 \ \ \ ,\ \ a_5 = 5 \ \ \ ,\ \ a_6 = 8 \ \ \ \ ,\ \ a_7 = 13\ \ \ \ ,\ \ a_8 = 21\ \ \ \ ,\ \ a_9 = 34 a1=1 , a2=1 , a3=2 , a4=3 , a5=5 , a6=8 , a7=13 , a8=21 , a9=34
因此,对于递归式 f i b ( n , a , b ) fib(n , a,b) fib(n,a,b),的递归终止条件也确定了。在 n = 2 n = 2 n=2 时返回 b ,或者在 n = 1 n = 1 n=1 返回 a 都可以,下面是C++的代码实现:
#include
using namespace std;
/*
题目:无穷数列 1,1,2,3,5,13,21,34,55,..., 称为 Fibonacci 数列,计算第n位数列。
*/
int Fib_Reverse(int n, int a, int b)
{
if (n <= 2) return b;
else return Fib_Reverse(n - 1, b, a + b);
}
int NiceFib(int n)
{
int a = 1, b = 1;
return Fib_Reverse(n, a, b);
}
int main()
{
for (int i = 1; i < 10; ++i)
{
cout << NiceFib(i) << " ";
}
cout << endl;
return 0;
}
除了直接求斐波那契数列的问题外,还有很多其衍生问题。如:求杨辉三角问题、爬楼梯问题、兔子繁殖问题、青蛙跳台阶问题等等。另外,卢卡斯数列 1、3、4、7、11、18…,也具有斐波那契数列同样的性质。值得一提的是在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用。感兴趣的同学可以自行在网上查找相关资料。
最后,如果觉得我的文章对你有帮助的话请帮忙点个赞,你的鼓励就是我学习的动力。如果文章中有错误的地方欢迎指正,有不同意见的同学也欢迎在评论区留言,互相学习。