fibonacci数列_几种计算Fibonacci数列算法的时间复杂度分析

今天我在timeline上刷到了@Pika369 的这篇文章:

https://zhuanlan.zhihu.com/p/165877869​zhuanlan.zhihu.com

文章挺详细,还跑了实验,但这是我人生中第

次看到有人犯将大数算术运算的复杂度视为
的“错误”了
[1] [2],所以忍不住多说两句。虽然这确实是一个常见的计算模型,但几乎无法准确地反映现实。如果用它来分析算法的实际运行时间,将很容易错过算法的真正瓶颈。

在一个更贴近现实的计算模型中,将两个

位大整数相加需要
的时间,而大整数相乘需要
(暴力算法)或
(FFT)的时间。python的乘法使用的是Karatsuba算法
[3],复杂度为
,下文简记为
。可以看出,单纯按照“进行大整数运算的次数”来衡量算法复杂度是极不准确的,还需要考虑到大整数的长度以及算术运算的种类。知道了这点之后,我们可以重新分析一下原文中提到的各个计算Fibonacci数列算法的复杂度,并与实际运行时间进行对照
[4]

算法一:暴力递推

def 

根据fibonacci数的通项公式,我们可以看出第

个fibonacci数的长度为
。这里我们做了
次大数加法,总复杂度为

时运行时间为1.10s。把递归改成递推解决爆栈问题后跑
的数据还是小看它了。

算法零:通项公式法

经评论区提醒补充分析一下这个,

从数值计算的角度来说,根据数值分析的知识可知只需要对运算过程中涉及到的实数保留

位精度就可以了。高精度实数开根可以在
时间内完成,而exp可以用AGM方法在
时间内完成
[5],所以总复杂度为

从符号计算的角度来说,因为域扩张

是一个有限扩张,所以维护下
前的系数就可以了
[6]。我们知道最终运算结果是整数,所以最后
前的系数一定为0,不会产生精度问题(
就是一个可怜的工具人)。实际运算和快速幂是等价的。

这里有用mpfr库写的C++代码,还是很快的,不过我们就不欺负python了。

算法二:矩阵的快速幂

import 

把快速幂看成递归的话,递归深度为

,大整数的算术运算总次数也为
。但注意到递归过程中大整数的长度呈几何级数变化
[7],总复杂度为
,即一次
位大整数乘法的常数倍。

时运行时间为81.6s,可以看出算法的常数比较大。

算法三:Fibonacci的一组恒等式

。这个算法是Dijkstra在1978年发现的。
from 

复杂度的计算和算法二类似,因为递归过程中大整数的长度呈几何级数变化,总复杂度也为

时运行时间为5.53s,常数比算法二小了很多。

注:原文代码在我本机的运行时间为5.71s,并且实际上“预先生成字典”对运行时间并没有什么影响。(与原作者确认过了)

算法3.1:

这里以对算法三的一个小改动为例,展示学会正确的复杂度分析可以如何帮助我们优化算法。

注意到算法三虽然进行了

次大整数运算,但复杂度的瓶颈为最后一次乘法,这与实验结果是相符的:最后对
的计算(根据
的奇偶性需要1~2次乘法,这里为1次)花费了2.42s,占据了总运行时间的44%。可想而知,只要能在最后这次乘法上优化一点常数,总时间也可以被提升一个常数。这时候,我们需要一点关于凑因式分解的初中知识:

先考虑

的情形。想法是多递归一层:令
,则
。令
,直接用递归式计算的话这里需要一次长度为
的乘法,以及三次长度为
的乘法(
)。但我们可以换一种方法计算多项式的值:令
,则
。这样,长度为
的乘法被减少到了两次(计算
)。

其他情况同理。对于

对于

对于

def 

时运行时间为5.05s,仅为算法三的
91%

(

时运行时间比为
时为
时为
。)

最后可以用以上技巧重新实现算法三,将运行时间进一步降低到原来的88%。起到的效果和这篇文章[6]里用到的Cassini's formula是类似的,并且在最后包一层函数F4的idea也可以把那篇文章加速14%(

为偶数时)。
def 

注:写文章的时候我不小心用32位python跑的,所以所有运行时间都那么慢。改回64位之后所有瓶颈是乘法的代码都可以快4倍左右,就不改正文了。

参考

  1. ^第n-1次应该是在这个问题下:斐波那契数列的第一百万项怎么用 C++ 求? https://www.zhihu.com/question/292320341
  2. ^原文的参考文章还是明确提到了计算模型的,所以没有问题。 https://www.nayuki.io/page/fast-fibonacci-algorithms
  3. ^CPython的code:  https://hg.python.org/cpython/file/b514339e41ef/Objects/longobject.c#l2694
  4. ^我按照自己的习惯对原文代码作了一些无伤大雅的小改动,原文作者可以验证它们确实是“无伤大雅”的,不会显著影响运行时间。
  5. ^https://en.wikipedia.org/wiki/Computational_complexity_of_mathematical_operations
  6. ^ab斐波那契数列第一千万项怎么求: https://zhuanlan.zhihu.com/p/98064307
  7. ^不少人没有注意到这一点,比如这个benchmark的复杂度分析就多了一个log n的项: http://cubbi.com/fibonacci.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值