算法 | 【斐波那契数列与递归】
斐波那契数列(Fibonacci sequence),又称黄金分割数列,当n趋向于无穷大时,前一项与后一项的比值越来越逼近黄金分割0.618(或者说后一项与前一项的比值小数部分越来越逼近 0.618)。
黄金比例和斐波那契数列的数学意义密切相关,在我们的生活中小到细胞分裂、花瓣的排列纹路,大到人口密度和土地面积测绘,甚至是宇宙星系都有着斐波那契数列的身影。
有关斐波那契数列的数学定理和相关公式请参考斐波那契数列——百度词条。其中斐波那契数列的排列方式很有趣,前两项的和等于第三项的和,我们很容易就可以依据这个特点写出它的递推公式: 。并且按照递推公式我们还可以推导出相应的通项公式:
下面让我们通过几个示例来了解斐波那契数列的魅力把。
示例1:无穷数列 1,1,2,3,5,13,21,34,55,…, 称为 Fibonacci 数列,计算第n位数列。
同样的,这次先用循环和递归做一遍,然后在想办法进行优化。
#include <iostream>
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;
}
分析递归效率:
对于循环来讲,它使用递推公式的方式求解,基本上没有需要优化的地方,除非我们选择使用通项公式求解,而对于当前的递归来说却还有着一些优化的空间。
递归过程分析:如:欲求的第五位斐波那契数,其递归调用方式如下
如图所示:
- 在第一次递归时,把求5的问题转换为求4的问题和求3的问题
- 在第二次递归时,把求4的问题转换为求3的问题和求2的问题
- 在第二次递归时,把求3的问题转换为求2的问题和求1的问题
- 在第三次递归时,把求3的问题转换为求2的问题和求1的问题
我们发现仅仅是计算第五个斐波那契数就要重复计算两次求3问题,三次求2问题,两次求1问题,这些重复的计算显然是没有意义的。从我们人的角度来分析问题,我们只需要计算一次“1”、一次“2”、一次“3”、一次“4”,就可以得到“5”的值了。并且我们也确实这样做了。正如我们所见到的,我们使用循环实现的求斐波那契数就是按照这种逻辑设计的。
前面提到过,通常情况下循环问题可以转换为递归问题,那么我们是否可以把循环式的代码稍加修改变成递归式呢?
循环式分析:
在循环中使用了三个变量,a、b 用于保存 、 的值,使用 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 开始计算,待到递归到结束时已经求得斐波那契数值。并且这个算法不存在重复求值问题。
递归调用式: ,调用过程中,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)
因此,对于递归式,的递归终止条件也确定了。在 时返回 b ,或者在 返回 a 都可以,下面是C++的代码实现:
#include <iostream>
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…,也具有斐波那契数列同样的性质。值得一提的是在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用。感兴趣的同学可以自行在网上查找相关资料。
最后,如果觉得我的文章对你有帮助的话请帮忙点个赞,你的鼓励就是我学习的动力。如果文章中有错误的地方欢迎指正,有不同意见的同学也欢迎在评论区留言,互相学习。