fib在python中什么意思,fib()相关的一些事

所有代码均来自于Python 2.7 版本

相信对于所有有过编程经历的童鞋而言,递归都是一个再熟悉不过的概念。而在初学递归的时候,相信斐波那契数列都是一个重要的例子(另一个则是汉诺塔(Hanoi))。今天就利用求第n项斐波那契数列作为一个例子,来简单说一下我对几个概念的理解。

递归

话不多说,直接上代码就好

def fib(n):

if n<2:

return 1

else:

return fib(n-1) + fib(n-2)

//再简单点

fib = lambda x:x if x<2 else fib(x-1)+fib(x-2)

稍有一点递归概念的童鞋都不难理解这几行程序,这也是递归的优势:简单,易编写。但同时,它也有一个非常致命的缺点,就是运行效率低(具体原因是存在大量的重复运算,具体过程可以手动算一个fib(10)体会一下,看看fib(1)到底调用了多少次)。

更为致命的是,Python还对递归栈的深度做了限制,也就是说稍微大一点的数都会抛出异常。

//对于上面已经定义好的fib函数进行调用

>>> fib(1000)

...

RuntimeError: maximum recursion depth exceeded

显然,1000已经超出了Python的最大递归深度。那么,有没有什么方法对递归进行优化呢?

迭代

一般认为,所有的递归都可以写成迭代,区别在于编程时间的花费以及代码量、可读性方面的问题。

简单思考一下,写出下面代码应该不难。

def fib(n):

a, b = 1, 1

if n<3:

return b

for _ in range(3, n):

a, b = b, a+b

return b

很显然,和递归的思考方式相反,递归是从顶向下,减而治之;迭代是从底向上,依次计算。

尾递归

尾递归是递归的一种,区别在于内存的占用(即递归栈深度的限制):尾递归只占用恒量的内存;而普通递归则是创建递归栈之后计算,随着输入数据的增大,引发递归深度增大,因而内存占用增大。详细解释

接下来就对上面的递归版本的fib()进行一个优化。

def fib(count, tmp=1, next_step=1):

if count < 2:

return tmp

else:

return fib(count-1, next_step, next_step+tmp)

其中,最后一行代码才是尾递归优化的部分。可以看出,相比普通的递归函数,尾递归每次只返回一个fib()并且通过count递减来保证减而知之和避免重复计算。

下面,就让我们来试一下这个版本的fib()。

//对于上面已经定义的尾递归版本fib函数进行调用

>>> fib(1000)

...

RuntimeError: maximum recursion depth exceeded

意外发现,竟然和普通递归抛出一样的异常。这是什么原因呢?简单搜索一下,悲剧的得知,Python在语言层面,并不支持尾递归优化且对递归次数有限制。

Yield

那么,就没有办法对于大规模输入使用递归写法了吗?

答案是有的。想想看,递归之所以无法工作,根本原因在于内存的大量占用和递归深度超限。所以说,只要削减递归占用的内存,就可以使用递归写法,享受递归带来的方便了。

yield关键字恰好是具有这种功能的解决方案。它产生一个生成器,它具有惰性求值的属性。对内存的占用是常数级别的,不随输入规模增大而增大。

def fib(count, tmp=1, next_step=1):

if count < 2:

yield tmp

else:

yield fib(count-1, next_step, next_step+tmp)

调用的时候

b = fib(1000)

for _ in xrange(1000):

b = b.next()

print b

效率

最后,利用运行时间,来比较一下迭代算法和尾递归的效率。

首先,写一个计算运行时间的装饰器。

def timeit(fun):

def wrapped(*args, **kwargs):

import time

t1 = time.time()

execFun = fun(*args, **kwargs)

//为了体现差别,让函数执行1000次

for _ in xrange(1000):

fun(*args, **kwargs)

t2 = time.time()

print t2 - t1

return execFun

return wrapped

def fib1_helper(count, tmp=1, next_step=1):

if count < 2:

yield tmp

else:

yield fib1_helper(count-1, next_step, next_step+tmp)

@timeit

def fib1(n):

b = fib1_helper(n)

for _ in xrange(n):

b = b.next()

@timeit

def fib2(n):

a, b = 1, 1

if n<3:

return b

for _ in range(3, n):

a, b = b, a+b

return b

//分别调用

>>> fib1(1000)

>>> fib2(1000)

//以下执行结果因执行环境不同可能存在差异

0.641000032425

0.0920000076294

可以看出,相比递归,迭代在执行时间上还是有着不小的优势。但是尾递归+yield的组合实现了递归写法,在某些问题上降低了编程时间的消耗,在某些场景下可以考虑作为一种新的思路。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值