说明:
用mackbookpro i7 2.7GHZ笔记本进行测试,结果如下:
备注: 当n=80时,由于测试等待时间过长,强制中断了执行。
从测试结果看出,当n逐渐增大,递归方式计算斐波拉契数列的时间复杂性急剧增加。当n值较大时可以考虑用循环方式代替。
类似的方式也可以用于,求阶乘、遍历目录、汉诺塔等问题的解决。在后期的文章中,我将这些内容进行补充,敬请期待,谢谢。
……续上回 Fibonacci数列高效解法大全及时间复杂度分析 连载【4】
来看profile的记录分析,看时间具体用在哪个部分了
一看,绝大部分时间耗在两句results上了
看来主要都用来大整数运算了
下面来试一下
把这程序里两句“results = ”后面的大数运算注释掉,换成1。也就是两句都成“results = 1”
再运行计时看看
Total time: 0.000753秒
很惊人,去掉大数运算后,运行时间缩短成了原用时的1%。也就是99%时间消耗在Python内置的大数运算上了
下面试下用号称地球上最好的大数运算库替换掉Python内置的大数运算
9. 应用GMP库
全称是GNU Multiple Precision Arithmetic Library,即GNU高精度算术运算库,这是一个C写成的高效大数运算库
gmpy2是Python下对GMP库的封装
安装很简单,在操作系统下打命令pip install gmpy2,就安装好了
应用到程序也很简单
把上面的二分迭代解法程序开头添加一行
再把程序里
改成
就可以了
运行看一下用时
Total time: 0.00689297秒
是原用Python内置大数运算用时的9%
效果显著。可见Python内置大数运算效率确实不怎么样
相关大整数乘法高效算法的介绍可参见这篇《 【算法】大数乘法问题及其高效算法 》
极大整数乘法的时间复杂度低至近似O(n*log n)
前面二分解法本身时间复杂度是O(log n)
现在把大数因素考虑进去。大数时间复杂度的n可以用二进制位数表示
第n项斐波那契数的二进制位数k跟n是线性关系,n*10,那位数k也是*10
现在把极大整数乘法时间复杂度代入,O(n*log n)*O(log n)=O(n*(log n)^2)
也就是在大数情况下二分解法的时间复杂度为O(n*(log n)^2)
可以看这篇《 为什么算法渐进复杂度中对数的底数总为2 》解释
10. 矩阵解法
斐波那契数列和矩阵的关系推导我看到GoCalf Blog里写的一段非常清晰,特在此引用
这解法就是求矩阵的n-1次幂。矩阵幂运算也能根据下面公式迭代二分加速
就是所谓的矩阵快速幂
Python里库很丰富,大名鼎鼎的numpy就是一个有关矩阵的库。这库是有优化的,算矩阵幂就不用个人再写什么矩阵快速幂函数了
用numpy库就能很简单的写出来
因为numpy没有大数支持,大数运算还是要用GMP库
同上测用时
Total time: 0.042466秒
这幂运算是二分加速的,时间复杂度为O(log n)
对于固定阶矩阵相乘,乘的次数是个常数,也就是O(1)。虽然这个常数比较大^_*
代入大数时间复杂度,总体复杂度也是O(n*(log n)^2)
这儿来解释下为何矩阵快速幂比二分递归解法时间常数大
我们再来仔细看看斐波那契数列的矩阵形式:
会发现 z 和 y 必然相等,z 没必要再计算一遍。
t = x - y,因此 t 也没必要再计算一遍。
只需要计算矩阵第一列的那两个元素即可:
矩阵快速幂中两个矩阵相乘实际可分解为8次两个大整数乘法,而二分递归中只需要3次两个大整数乘法。所以二分递归时间常数小。
未完待续……
Fibonacci数列高效解法大全及时间复杂度分析 连载【6】
仅供参考吧ASSUME CS:CODE,DS:DATA
DATA SEGMENT
BUFF DB 10
DB ?
DB 10 DUP(?)
RESULT DW ?
RESULT_SHOW DB 10 DUP(?)
DATA ENDS
CODE SEGMENT
START:
MOV AX,DATA
MOV DS,AX
LEA DX,BUFF
MOV AH,0AH
INT 21H
MOV DI,0
L0: 统计一共有多少个数字组成
CMP BYTE PTR DS:[DI+2],0DH
JZ GO
INC DI
JMP L0
GO: 计算第n个斐波那契数,把数字字符串转换为十进制数
MOV BL,10
MOV AX,1
MOV SI,DI 为后面判断输入的是不是只输入一个数有用
MOV CX,DI
L2: PUSH AX
SUB BYTE PTR DS:[DI+1],30H
MUL BYTE PTR DS:[DI+1]
ADD RESULT,AX
POP AX
MUL BL
DEC DI
LOOP L2
分两种情况:1.输入的是1;2.输入的不是1
CMP SI,1
JNZ L7
CMP BYTE PTR RESULT,1
JNZ L7
MOV AX,RESULT
JZ L4
L7: MOV AX,1
MOV BX,0
MOV CX,RESULT
DEC CX
L3: 第n个斐波那契数存放到AX中
PUSH AX
ADD AX,BX
POP BX
LOOP L3
L4:
显示这个斐波那契数
MOV DX,0
LEA SI,RESULT_SHOW
MOV DI,0 利用DI来累计一共有多少个数字
L5:
MOV CX,10
CALL DIVDW
ADD CL,30H
MOV DS:[SI],CL
CMP AX,0
JZ L6
INC SI
INC DI
JMP L5
L6:
MOV DL,DS:[SI]
MOV AH,2
INT 21H
CMP DI,0
JZ OK
DEC SI
DEC DI
JMP L6
OK:
MOV AX,4C00H
INT 21H
参数: (AX)=DWORD型低16位数据
(DX)=DWORD型高16位数据
(CX)=除数
返回: (DX)=结果的高16位,(AX)=结果的低16位
(CX)=余数
32位除16位,可以防止溢出!
DIVDW: 子程序定义开始,功能是分离各个数字出来
PUSH AX
MOV AX,DX
MOV DX,0
DIV CX
MOV BX,AX
POP AX
DIV CX
MOV CX,DX
MOV DX,BX
RET 子程序定义结束
CODE ENDS
END START