兔子问题---细说斐波那契数列


对于兔子问题的鼎鼎大名,相信很少有人没听过吧!为了完整性还是再说一下题目吧!

题目描述已知一对兔子每一个月可以生一对小兔子,而一对兔子出生后.第三个月开始生小兔子假如一年内没有发生死亡,则一对兔子开始,第N个月后会有多少对?

这道题所描述的就是斐波那契数列啦!这里以一对为单位,那么,从第一个月开始,每个月总共的兔子数量就是1,1,2,3,5,8,13......可以看出前两个月为1,从第三个月开始,当月的数量为前两个月数量之和,所以可以形成公式f(n)=f(n-1)+f(n-2)(n>2)同时f(1)=1,f(2)=1。

关于这道题的实现由很多种方法,那么接下来笔者就从O(2^n)说起。

1.时间复杂度O(2^n)

#include<iostream>
using namespace std;
int Fibonacci(int fib)
{
	if(fib<=0)
	{
		return 0;
	}
	else if(fib==1||fib==2)
	{
		return 1;
	}
	else{
		return Fibonacci(fib-1)+Fibonacci(fib-2);//当前月等于前一个月的数量加上前两个月的数量
	}
}
int main()
{
	int num,result;
	cin>>num;//输入第几个月
	result=Fibonacci(num);
	cout<<result;
	return 0;
}
那为什么说此方法的时间复杂度是O (2^n)呢?

下图描述了整个递归过程,可以看出最终形成了类似一个二叉树,每个节点都有访问,那么时间复杂度,就明显看的出来了



上图也同样暴露出上述递归算法的问题所在,就是同一个数多次访问,明明其它地方计算过了,却还会重复计算。

接下讲述另一种方法,时间复杂度有较大改善

2.时间复杂度O(n)

#include<iostream>
using namespace std;
int Fibonacci(int fib)
{
	int res=1;
	int pre=1;
	int temp=0;
	if(fib<=0)
	{
		return 0;
	}
	else if(fib==1||fib==2)
	{
		return 1;
	}
	else
	{
	   for(int i=3;i<=fib;i++)
	   {
		   temp=res+pre;
		   pre=res;
		   res=temp;
	   }
	   return res;
	}
}
int main()
{
	int num,result;
	cin>>num;//输入第几个月
	result=Fibonacci(num);
	cout<<result;
	return 0;
}
很明显可以看出整个时间消耗在i 在3  - fib 的循环中,时间复杂度O(n),自此斐波那契数列已经达到线性,那么,时间复杂度有没有可能更小呢?

答案是肯定的。

3.时间复杂度O(log(n)) (本方法学习自牛客网,左程云(左神))

当然在讲述这种方法之前,必须要介绍一下线性代数的知识,在线性代数中下图中的等式是恒成立的。



具体怎么证明,直接说,不会,但是这个公式确实整个算法的核心,由上面的公式我们可以列出如下图的公式并且得出a,b,c,d的值。



那么,下图所示的推导过程也就很容易理解了


现在想求F(n)整个问题就变成了一个矩阵的(n-2)次方了,那么一个矩阵的(n-2) 次,实现起来怎么达到 O(log(n))?
以下引用左神原话:
而求矩阵N次⽅方的问题,明显是一个更够在O(logN)时间内解决的问题。为了表述方便,我们现在用求一个整数N次方的例子来说明,因为只要理解了如何在O(logN)的时间复杂度内求整数N次方的问题,对于求矩阵N次方的问题是同理的,区别是矩阵乘法和整数乘法在细节上有些不一样,但是对于怎么乘更快,两者的道理相同。
假设一个整数是10,如何最快的求解10的75次方。
1,75的二进制形式为1001011。
2,10的75次方=(10^64) * (10^8) * (10^2) * (10^1)。在这个过程中,我们先求出10^1,然后根据10^1求出10^2,再根据10^2求出10^4,...,最后根据10^32求出10^64次方,即75的二进制形式总共有多少位,我们就使用了几次乘法。
3,在步骤2进行的过程中,把应该累乘的值乘起来即可。10^64、10^8、10^2、10^1应该累乘起来,因为64、8、2、1对应到75的二进制中,相应的位上是1。而10^32、10^16、10^4不应该累乘,因为32、16、4对应到75的二进制中,相应的位上是0。
整体程序实现如下:
#include<iostream>
using namespace std;
int base[4] = { 1, 1, 1, 0 };
int result[4] = { 1, 0, 0, 1 };//将结果矩阵初始为单位阵E

void MatrixMulti(int ba[4], int re[4], bool flag)// 这个函数实现其实应该用二维数组,原谅我这一生放纵不羁爱偷懒
{
	int temp[4] = { 0 };
	temp[0] = ba[0] * re[0] + ba[1] * re[2];
	temp[1] = ba[0] * re[2] + ba[1] * re[3];
	temp[2] = ba[2] * re[0] + ba[3] * re[2];
	temp[3] = ba[2] * re[1] + ba[3] * re[3];
	if (flag)
	{
		for (int i = 0; i < 4; i++)
		{
			result[i] = temp[i];
		}
	}
	else
	{
		for (int i = 0; i < 4; i++)
		{
			base[i] = temp[i];
		}
	}
}
int main()
{
	
	int month;
	cin >> month;
	if (month < 1)
	{
		cout << 0 << endl;
		return 0;
	}
	if (month == 1||month==2)
	{
		cout << 1 << endl;
		return 0;
	}
	month-=2;
	for (; month != 0; month >>= 1)//此for循环是实现O(log(n))的关键
	{
		if (month & 1)
		{
			MatrixMulti(base, result,true);
		}
		MatrixMulti(base, base,false);
	}
	cout << result[0] + result[2];
	return 0;
}
那么到这就完了么,时间复杂度还能不能再降低呢?答案是,能。

4.时间复杂度O(1) 
之所以能实现O(1) 请感谢数学的伟大吧,竟然有人计算出了斐波那契数列的通项公式 
那么时间复杂度必须是O(1)啊!先不着急程序实现,我们想想,如果问我们怎么判断一个数是不是斐波那契数呢?同样也有 O(1)的解法:判断一个数是否是一个斐波那契数当且仅当5N^2+4或5N^2-4是平方数。我只能再一次感叹数学的伟大,所以数学系的经常来抢计算机的饭碗,我们却无能为力啊!下面给出代码实现:
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
void main()
{
 double n;
 scanf("%lf",&n);
 printf("%d\n", (int)((pow(((1 + sqrt(5)) / 2), n) - pow(((1 - sqrt(5)) / 2), n)) / sqrt(5)));
 return 0;
}

结语:看见最后这几行代码我想会有很多人哭晕在厕所吧!我先哭一会!!!

  • 13
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值