go语言 使用递归与循环两种方式计算斐波那契数列

Python025

go语言 使用递归与循环两种方式计算斐波那契数列,第1张

给定一个正整数n计算出对应斐波那契数列对应的值

说明:

用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