python斐波那契数列递归算法的时间复杂度推导_几种计算Fibonacci数列算法的时间复杂度分析...

今天我在timeline上刷到了@Pika369 的这篇文章:https://zhuanlan.zhihu.com/p/165877869​zhuanlan.zhihu.com

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

次看到有人犯将大数算术运算的复杂度视为

的“错误”了

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

位大整数相加需要

的时间,而大整数相乘需要

(暴力算法)或

(FFT)的时间。python的乘法使用的是Karatsuba算法

,下文简记为

。可以看出,单纯按照“进行大整数运算的次数”来衡量算法复杂度是极不准确的,还需要考虑到大整数的长度以及算术运算的种类。知道了这点之后,我们可以重新分析一下原文中提到的各个计算Fibonacci数列算法的复杂度,并与实际运行时间进行对照

算法一:暴力递推

def F1(n):

a,b=1,1

for i in range(n-2): a,b=b,a+b

return b

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

个fibonacci数的长度为

。这里我们做了

次大数加法,总复杂度为

时运行时间为1.10s。把递归改成递推解决爆栈问题后跑

的数据还是小看它了。

算法零:通项公式法

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

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

位精度就可以了。高精度实数开根可以在

时间内完成,而exp可以用AGM方法在

时间内完成

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

是一个有限扩张,所以维护下

前的系数就可以了

前的系数一定为0,不会产生精度问题(

就是一个可怜的工具人)。实际运算和快速幂是等价的。

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

算法二:矩阵的快速幂

import sympy as sym

def Matrix_fastPower(A,n):

size = A.shape[0]

result = sym.eye(size)

base = A

power = n

while power > 0:

if power % 2 == 1:

result = result * base

power = power // 2

base = base * base

return result

def F2(n):

return Matrix_fastPower(sym.Matrix([[1,1],[1,0]]),n)[1,0]

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

,大整数的算术运算总次数也为

。但注意到递归过程中大整数的长度呈几何级数变化

,即一次

位大整数乘法的常数倍。

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

算法三:Fibonacci的一组恒等式

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

from functools import lru_cache

@lru_cache(None)

def F3(n):

if n == 1 or n == 2:

return 1

k = n // 2

a = F3(k)

b = F3(k + 1)

if n % 2:

return a * a + b * b

else:

return a * (2 * b - a)

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

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

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

算法3.1:

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

注意到算法三虽然进行了

次大整数运算,但复杂度的瓶颈为最后一次乘法,这与实验结果是相符的:最后对

的计算(根据

的奇偶性需要1~2次乘法,这里为1次)花费了2.42s,占据了总运行时间的44%。可想而知,只要能在最后这次乘法上优化一点常数,总时间也可以被提升一个常数。这时候,我们需要一点关于凑因式分解的初中知识:

先考虑

的情形。想法是多递归一层:令

,则

。令

,直接用递归式计算的话这里需要一次长度为

的乘法,以及三次长度为

的乘法(

)。但我们可以换一种方法计算多项式的值:令

,则

。这样,长度为

的乘法被减少到了两次(计算

)。

其他情况同理。对于

对于

对于

def F4(n):

a=F3(n//4)

b=F3(n//4+1)

t=a*(2*b-a)

t1=b*(2*a+b)

if n%4==0:

return t*(t1*2-3*t)

elif n%4==1:

c=t1-t

return t*t+c*c

elif n%4==2:

return (t1-t)*(t1+t)

else:

c=t1-t

return c*c+t1*t1

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

(

时运行时间比为

时为

时为

。)

最后可以用以上技巧重新实现算法三,将运行时间进一步降低到原来的88%。起到的效果和这篇文章14%(

为偶数时)。

def F3_(n):

if n==0: return 0,1

a,b=F3_(n//2)

t=a*(2*b-a)

t1=b*(2*a+b)

if n%2==0: return t,t1-t

else: return t1-t,t1

def F4_(n):

a,b=F3_(n//4)

t=a*(2*b-a)

t1=b*(2*a+b)

if n%4==0: return t*(t1*2-3*t)

elif n%4==1: return t*t+(t1-t)**2

elif n%4==2: return (t1-t)*(t1+t)

else: return (t1-t)**2+t1*t1

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

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值