斐波那契数列的算法优化
2017-05-10 13:50
225 查看
斐波那契数列,但凡学过编程的童鞋们应该都懂,背景就不介绍了(就是大兔子生小兔子的故事),无论是面试还是实际的运用,常见的一个思路就是先用最先基本的办法实现,然后根据实际要求,一步步改进,优化算法效率。今天就以斐波那契数列这个大家都很熟悉的为例来小小感受一下
Version 1
long Fibonacci(int n)
{
if (n == 0)
return 0;
else if (n == 1)
return 1;
else if (n > 1)
return Fibonacci (n - 1) + Fibonacci (n - 2);
else
return -1;
}这是最基本的递归思路,大家的第一个斐波那契数列应该都是写成这样的,但是不知道大家有没有测试过它的性能如何,不测不知道,一测吓一跳,N=1000,500,100,50都是黑窗口半天跳不出数据,看CPU是100%(我的电脑是25%,但是因为我的电脑是4核的,大家懂的),最后N=45得到的结果是145秒。 性能为什么会这么低呢?以N=5为例子,该程序的执行过程如下图所示,可以想象,N到一定的数目(其实还是很小的数目,比如45.。。。)就会有很多次的递归调
用
那么,自然的想法是,有什么办法可以减少递归的次数呢?再观察上图,可以看出,有重复的递归调用。比如F(3)就计算过两次,一个解决的方法就是记录下来已经得到结果的F(n),避免重复多次计算。 Version 2 long tempResult[10001]={0};
long Fibonacci2(int n)
{
if (n == 0)
return 0;
else if (n == 1)
return 1;
else if (n > 1)
{
if(tempResult
!= 0)
return tempResult
;
else
{
tempResult
= Fibonacci2 (n - 1) + Fibonacci2 (n - 2);
return tempResult
;
}
}
} 这次优化之后,效率明显提高,N=1000时,运行时间仍趋近于0秒,但是当N=5000时出现了栈溢出的情况。再来分析,栈溢出,那么栈当中有什么呢?调用信息,变量。我们version2的改进,是将version1的调用树砍掉了一半,所以,要真正解决这个问题,还是要放弃递归算法。大家应该了解,常见的改变递归算法的方式是将它变成循环。实际上,递归是从大往小分解问题,循环则是反方向算法。
现在,当N=1000000的时候,时间仍然小于1秒了。 当然,问题还没有结束,虽然version3看上去已经是一个效率很好的算法了。前面的解决方式都是自然的从算法角度来考虑,但是,数学的力量是伟大的。version3的复杂度是O(n),有没有对数级的算法,或者更好的,常量时间算法呢? 回归到高中数学,发现f(n)=f(n-1)+f(n-2)是一个数列的通项公式,经过化简,我们可以得到它的递推公式,可以一步得出结果,为O(1)的时间复杂度。但是由于最后的递推公式中含有无理数,所以不能保证结果的精度。 还有没有别的解法呢?有没有对数时间的解法?要对数时间,就要使用分治二分策略。有这个方向,但是没有想出来。当然肯定有牛人想出来的,大家感兴趣的给个链接看看http://hi.baidu.com/houtangcaicai/blog/item/aa40e31a6160cc71dab4bdfd.htm
Version 1
long Fibonacci(int n)
{
if (n == 0)
return 0;
else if (n == 1)
return 1;
else if (n > 1)
return Fibonacci (n - 1) + Fibonacci (n - 2);
else
return -1;
}这是最基本的递归思路,大家的第一个斐波那契数列应该都是写成这样的,但是不知道大家有没有测试过它的性能如何,不测不知道,一测吓一跳,N=1000,500,100,50都是黑窗口半天跳不出数据,看CPU是100%(我的电脑是25%,但是因为我的电脑是4核的,大家懂的),最后N=45得到的结果是145秒。 性能为什么会这么低呢?以N=5为例子,该程序的执行过程如下图所示,可以想象,N到一定的数目(其实还是很小的数目,比如45.。。。)就会有很多次的递归调
用
那么,自然的想法是,有什么办法可以减少递归的次数呢?再观察上图,可以看出,有重复的递归调用。比如F(3)就计算过两次,一个解决的方法就是记录下来已经得到结果的F(n),避免重复多次计算。 Version 2 long tempResult[10001]={0};
long Fibonacci2(int n)
{
if (n == 0)
return 0;
else if (n == 1)
return 1;
else if (n > 1)
{
if(tempResult
!= 0)
return tempResult
;
else
{
tempResult
= Fibonacci2 (n - 1) + Fibonacci2 (n - 2);
return tempResult
;
}
}
} 这次优化之后,效率明显提高,N=1000时,运行时间仍趋近于0秒,但是当N=5000时出现了栈溢出的情况。再来分析,栈溢出,那么栈当中有什么呢?调用信息,变量。我们version2的改进,是将version1的调用树砍掉了一半,所以,要真正解决这个问题,还是要放弃递归算法。大家应该了解,常见的改变递归算法的方式是将它变成循环。实际上,递归是从大往小分解问题,循环则是反方向算法。
long Fibonacci3(int n) { long * temp = new long[n + 1]; temp[0] = 0; if (n > 0) temp[1] = 1; for(int i = 2; i <= n; ++i) { temp[i] = temp[i - 1] + temp[i - 2]; } long result = temp ; delete[] temp; return result; }
现在,当N=1000000的时候,时间仍然小于1秒了。 当然,问题还没有结束,虽然version3看上去已经是一个效率很好的算法了。前面的解决方式都是自然的从算法角度来考虑,但是,数学的力量是伟大的。version3的复杂度是O(n),有没有对数级的算法,或者更好的,常量时间算法呢? 回归到高中数学,发现f(n)=f(n-1)+f(n-2)是一个数列的通项公式,经过化简,我们可以得到它的递推公式,可以一步得出结果,为O(1)的时间复杂度。但是由于最后的递推公式中含有无理数,所以不能保证结果的精度。 还有没有别的解法呢?有没有对数时间的解法?要对数时间,就要使用分治二分策略。有这个方向,但是没有想出来。当然肯定有牛人想出来的,大家感兴趣的给个链接看看http://hi.baidu.com/houtangcaicai/blog/item/aa40e31a6160cc71dab4bdfd.htm
相关文章推荐
- 斐波那契数列算法优化问题
- 斐波那契数列(二)--矩阵优化算法
- 斐波那契数列算法优化
- TLD跟踪算法优化(一)并行化
- 海量数据库的查询优化及分页算法方案
- 多线程队列算法优化(双端队列)(一
- 海量数据库的查询优化及分页算法方案
- 机器学习(11.4)--神经网络(nn)算法的深入与优化(4) -- CorssEntropyCost(交叉熵代价函数)数理分析与代码实现
- LeetCode Linked List Cycle II 和I 通用算法和优化算法
- 机器学习优化算法—L-BFGS
- 深度学习笔记——TensorFlow学习笔记(二)激活函数、损失函数、优化算法和正则项
- 【C#代码实战】群蚁算法理论与实践全攻略——旅行商等路径优化问题的新方法
- 从一道笔试题谈算法优化(下)
- SQLServer海量数据库的查询优化及分页算法方案
- 算法细节系列(27):时间复杂度为何还能优化?
- 各种梯度优化算法介绍(SGD Loss剧烈波动)
- 海量数据库的查询优化及分页算法方案
- 【算法复习三】算法设计技巧与优化----算法设计技巧之中位数
- zz从一道笔试题谈算法优化(上)
- 转:海量数据库的查询优化及分页算法方案