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的组合实现了递归写法,在某些问题上降低了编程时间的消耗,在某些场景下可以考虑作为一种新的思路。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • 大学门口和宿舍之间有一片空地,入学时候那还是一片堆满泥渣、杂草乱长的泥地。虽然它满是黄泥灰烬,可因为按照门口...
    吾与鱼阅读 152评论 0 0
  • (一) 我什么时候最清醒? 刚睡醒的时候 和不想你的时候 二者都很难得 (二) 我可以排挤出所有的时间 只想和你 ...
    馒头小米粥阅读 261评论 1 1
  • 一、学经汇报 学经日期:2017年5月9日 星期二 天气:阴晴不定,间中有小雨宝贝年龄:4周岁4个月和1周岁1个月...
    兜果妈阅读 244评论 2 1
  • 多维度打造竞争力:如果暂时没有足够突出的维度,那就好好打磨自己的技能;如果已经在某一维度足够优秀,不要沉浸...
    枫情物语阅读 137评论 0 1
  • 通过对ERP的理论学习,了解了ERP的相关知识,所谓ERP即企业资源计划(Enterprise Resource ...
    宋爽阅读 5,559评论 0 5