对于兔子问题的鼎鼎大名,相信很少有人没听过吧!为了完整性还是再说一下题目吧!
题目描述:已知一对兔子每一个月可以生一对小兔子,而一对兔子出生后.第三个月开始生小兔子假如一年内没有发生死亡,则一对兔子开始,第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。
假设一个整数是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;
}
结语:看见最后这几行代码我想会有很多人哭晕在厕所吧!我先哭一会!!!