数据结构与算法 | 【斐波那契数列与递归】

斐波那契数列(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=an1+an2 。并且按照递推公式我们还可以推导出相应的通项公式:

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=5 1[(21+5 )n(215 )n]()

下面让我们通过几个示例来了解斐波那契数列的魅力把。

示例1:无穷数列 1,1,2,3,5,13,21,34,55,…, 称为 Fibonacci 数列,计算第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;
}

分析递归效率
对于循环来讲,它使用递推公式的方式求解,基本上没有需要优化的地方,除非我们选择使用通项公式求解,而对于当前的递归来说却还有着一些优化的空间。
递归过程分析:如:欲求的第五位斐波那契数,其递归调用方式如下

1递归
1递归
2递归
2递归
2递归
2递归
3递归
3递归
1
2
3
4
5
.3
2.
.2
.1

如图所示:

  • 在第一次递归时,把求5的问题转换为求4的问题和求3的问题
  • 在第二次递归时,把求4的问题转换为求3的问题求2的问题
  • 在第二次递归时,把求3的问题转换为求2的问题求1的问题
  • 在第三次递归时,把求3的问题转换为求2的问题求1的问题

我们发现仅仅是计算第五个斐波那契数就要重复计算两次求3问题,三次求2问题,两次求1问题,这些重复的计算显然是没有意义的。从我们人的角度来分析问题,我们只需要计算一次“1”、一次“2”、一次“3”、一次“4”,就可以得到“5”的值了。并且我们也确实这样做了。正如我们所见到的,我们使用循环实现的求斐波那契数就是按照这种逻辑设计的。

前面提到过,通常情况下循环问题可以转换为递归问题,那么我们是否可以把循环式的代码稍加修改变成递归式呢?
循环式分析:
在循环中使用了三个变量,a、b 用于保存 a n − 1 a_{n-1} an1 a n − 2 a_{n-2} an2 的值,使用 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(n1,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…,也具有斐波那契数列同样的性质。值得一提的是在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用。感兴趣的同学可以自行在网上查找相关资料。

最后,如果觉得我的文章对你有帮助的话请帮忙点个赞,你的鼓励就是我学习的动力。如果文章中有错误的地方欢迎指正,有不同意见的同学也欢迎在评论区留言,互相学习。

——学习路上,你我共勉

你可能感兴趣的:(数据结构与算法,算法,斐波那契数列,递归)