1.递归函数

在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。

举个例子,我们来计算阶乘n! = 1 x 2 x 3 x ... x n,用函数fact(n)表示,可以看出:

fact(n) = n! = 1 x 2 x 3 x ... x (n-1) x n = (n-1)! x n = fact(n-1) x n

所以,fact(n)可以表示为n x fact(n-1),只有n=1时需要特殊处理。

于是,fact(n)用递归的方式写出来就是:

  1. def fact(n):
  2. if n==1:
  3. return 1
  4. return n * fact(n - 1)

上面就是一个递归函数。可以试试:

  1. >>> fact(1)
  2. 1
  3. >>> fact(5)
  4. 120
  5. >>> fact(100)
  6. 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L

如果我们计算fact(5),可以根据函数定义看到计算过程如下:

  1. ===> fact(5)
  2. ===> 5 * fact(4)
  3. ===> 5 * (4 * fact(3))
  4. ===> 5 * (4 * (3 * fact(2)))
  5. ===> 5 * (4 * (3 * (2 * fact(1))))
  6. ===> 5 * (4 * (3 * (2 * 1)))
  7. ===> 5 * (4 * (3 * 2))
  8. ===> 5 * (4 * 6)
  9. ===> 5 * 24
  10. ===> 120

递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

使用递归函数需要注意防止栈溢出。

2.栈溢出

在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。可以试试fact(1000)

  1. >>> fact(1000)
  2. Traceback (most recent call last):
  3. File "<stdin>", line 1, in <module>
  4. File "<stdin>", line 4, in fact
  5. ...
  6. File "<stdin>", line 4, in fact
  7. RuntimeError: maximum recursion depth exceeded

尾递归

解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:

  1. def fact(n):
  2. return fact_iter(1, 1, n)
  3.  
  4. def fact_iter(product, count, max):
  5. if count > max:
  6. return product
  7. return fact_iter(product * count, count + 1, max)

可以看到,return fact_iter(product * count, count + 1, max)仅返回递归函数本身,product * countcount + 1在函数调用前就会被计算,不影响函数调用。

fact(5)对应的fact_iter(1, 1, 5)的调用如下:

  1. ===> fact_iter(1, 1, 5)
  2. ===> fact_iter(1, 2, 5)
  3. ===> fact_iter(2, 3, 5)
  4. ===> fact_iter(6, 4, 5)
  5. ===> fact_iter(24, 5, 5)
  6. ===> fact_iter(120, 6, 5)
  7. ===> 120

尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。

遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。

优化尾递归的装饰器

有一个针对尾递归优化的decorator,可以参考源码:

  1. #!/usr/bin/env python2.4
  2. # This program shows off a python decorator(
  3. # which implements tail call optimization. It
  4. # does this by throwing an exception if it is
  5. # it's own grandparent, and catching such
  6. # exceptions to recall the stack.
  7.  
  8. import sys
  9.  
  10. class TailRecurseException:
  11. def __init__(self, args, kwargs):
  12. self.args = args
  13. self.kwargs = kwargs
  14.  
  15. def tail_call_optimized(g):
  16. """
  17. This function decorates a function with tail call
  18. optimization. It does this by throwing an exception
  19. if it is it's own grandparent, and catching such
  20. exceptions to fake the tail call optimization.
  21.  
  22. This function fails if the decorated
  23. function recurses in a non-tail context.
  24. """
  25. def func(*args, **kwargs):
  26. f = sys._getframe()
  27. if f.f_back and f.f_back.f_back \
  28. and f.f_back.f_back.f_code == f.f_code:
  29. raise TailRecurseException(args, kwargs)
  30. else:
  31. while 1:
  32. try:
  33. return g(*args, **kwargs)
  34. except TailRecurseException, e:
  35. args = e.args
  36. kwargs = e.kwargs
  37. func.__doc__ = g.__doc__
  38. return func
  39.  
  40. @tail_call_optimized
  41. def factorial(n, acc=1):
  42. "calculate a factorial"
  43. if n == 0:
  44. return acc
  45. return factorial(n-1, n*acc)
  46.  
  47. print factorial(10000)
  48. # prints a big, big number,
  49. # but doesn't hit the recursion limit.
  50.  
  51. @tail_call_optimized
  52. def fib(i, current = 0, next = 1):
  53. if i == 0:
  54. return current
  55. else:
  56. return fib(i - 1, next, current + next)
  57.  
  58. print fib(10000)
  59. # also prints a big number,
  60. # but doesn't hit the recursion limit.

现在,只需要使用这个@tail_call_optimized,就可以顺利计算出fact(1000)

  1. >>> fact(1000)
  2. 4023872600770937735437024339230039857193748642107146325437999104299385123986290205920442084869694048
    0047998861019719605863166687299480855890132382966994459099742450408707375991882362772718873251977950
    5950995276120874975462497043601418278094646496291056393887437886487337119181045825783647849977012476
    6328898359557354325131853239584630755574091142624174743493475534286465766116677973966688202912073791
    4385371958824980812686783837455973174613608537953452422158659320192809087829730843139284440328123155
    8611036976801357304216168747609675871348312025478589320767169132448426236131412508780208000261683151
    0273418279777047846358681701643650241536913982812648102130927612448963599287051149649754199093422215
    6683257208082133318611681155361583654698404670897560290095053761647584772842188967964624494516076535
    3408198901385442487984959953319101723355556602139450399736280750137837615307127761926849034352625200
    0158885351473316117021039681759215109077880193931781141945452572238655414610628921879602238389714760
    8850627686296714667469756291123408243920816015378088989396451826324367161676217916890977991190375403
    1274622289988005195444414282012187361745992642956581746628302955570299024324153181617210465832036786
    9061172601587835207515162842255402651704833042261439742869330616908979684825901254583271682264580665
    2676995865268227280707578139185817888965220816434834482599326604336766017699961283186078838615027946
    5955131156552036093988180612138558600301435694527224206344631797460594682573103790084024432438465657
    2450144028218852524709351906209290231364932734975655139587205596542287497740114133469627154228458623
    7738753823048386568897646192738381490014076731044664025989949022222176590433990188601856652648506179
    9702356193897017860040811889729918311021171229845901641921068884387121855646124960798722908519296819
    3723886426148396573822911231250241866493531439701374285319266498753372189406942814341185201580141233
    4482801505139969429015348307764456909907315243327828826986460278986432113908350621709500259738986355
    4277196742822248757586765752344220207573630569498825087968928162753848863396909959826280956121450994
    8717012445164612603790293091208890869420285106401821543994571568059418727489980942547421735824010636
    7740459574178516082923013535808184009699637252423056085590370062427124341690900415369010593398383577
    7939410970027753472000000000000000000000000000000000000000000000000000000000000000000000000000000000
    0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    00000000000000000000000000000000000000000000000000000000000000000000

Python中的栈溢出及解决办法的更多相关文章

  1. python中的TypeError错误解决办法

    新手在学习python时候,会遇到很多的坑,下面来具体说说其中一个. 在使用python编写面向对象的程序时,新手可能遇到TypeError: this constructor takes no ar ...

  2. 第一次打开pycharm运行python文件报错”No Python interpreter selected“问题的解决办法

    前面没有细讲,这里细述一下安装pycharm后,第一次打开pycharm运行python文件报错"No Python interpreter selected"问题的解决办法. 出 ...

  3. vsftp在REDHAT,CENTOS 5中登录慢的解决办法

    vsftp在REDHAT,CENTOS 5中登录慢的解决办法 vsftp在REDHAT,CENTOS 5中不仅登录慢,至少花30秒左右,而且上传文件的速度也受影响, 经过摸索,根本原因在DNS解析上花 ...

  4. linux中tomcat内存溢出解决办法

    用命令 tail -f /root/apache-tomcat-6.0.20/logs/catalina.out(需要找到tomcat路径) 查看日志,查看是否有错误 linux中tomcat内存溢出 ...

  5. ORA-12514:TNS:监听程序当前无法识别连接描述符中请求的服务解决办法

    ORA-12514:TNS:监听程序当前无法识别连接描述符中请求的服务解决办法: 1.首先打开cmd命令 查看本地TNSPING配置 是否ok?然后找到 Oracle 安装文件 中 listener. ...

  6. .net core, docker 在vs2019开发过程中的问题以及解决办法

    .net core, docker 在vs2019开发过程中的问题以及解决办法 记录下来,帮助Ta人~ 1.vs调试,快Build完后提示Docker 端口:xxxx,xxxx,xxxx占用 解决办法 ...

  7. 转:阿里旺旺导致python安装包失败的解决办法

    我以前使用web.py没事,今天运行时报错, mimetypes.init() # try to read system mime.types File "D:\ProgramFiles\p ...

  8. Flask中无法在其他函数中查询Sqlachemy的解决办法

    报错信息部分截取: File "D:\python 3.5\lib\site-packages\flask_sqlalchemy\__init__.py", line 912, i ...

  9. python—— 写入错误UnicodeEncodeError的解决办法

    在写python爬虫过程中,有时候吧结果写入到txt文件,但是会遇到UnicodeEncodeError. 错误原因—— 把文件内容,写入到文件中时,出错了. 而出错的原因其实是,python系统,在 ...

随机推荐

  1. NSData所有API学习

      www.MyException.Cn  网友分享于:2015-04-24  浏览:0次   NSData全部API学习. 学习NSData,在网上找资料竟然都是拷贝的纯代码,没人去解释.在这种网上 ...

  2. linux命令详解:pgrep命令

    转载:http://www.th7.cn/system/lin/201311/46742.shtml 前言    经常要查看进程的信息,包括进程的是否已经消亡,通过pgrep来获得正在被调度的进程的相 ...

  3. Mysql(二):库操作

    一 系统数据库 information_schema: 虚拟库,不占用磁盘空间,存储的是数据库启动后的一些参数,如用户表信息.列信息.权限信息.字符信息等performance_schema: MyS ...

  4. Flex进度条

    Flex中,进度条的皮肤,以及使用Timer让它自动增加~ mxml中: <mx:ProgressBar id="proBar" verticalCenter="0 ...

  5. 测试任务汇总v1.0

    2017.08.04 整理了目前我们所在团队需要做的日常任务 定义为v1.0

  6. maven中的profile文件的解析

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...

  7. 初识vue——语法初解

    这次我们按照官网上的教程对vue的语法进行一个初步的了解: 一.声明式渲染 Vue.js的核心是一个允许采用简洁的模板语法来声明式的将数据渲染仅DOM的系统: 1.我们在HelloWorld里面输入下 ...

  8. 深入研究Sphinx的底层原理和高级使用

    深入研究Sphinx的底层原理和高级使用

  9. PHP安全、Sql防注入安全汇总

    利用Mysqli和PDO 产生原因 主要就是一些数据没有经过严格的验证,然后直接拼接 SQL 去查询.导致漏洞产生,比如: $id = $_GET['id']; $sql = "SELECT ...

  10. 1×1卷积的用途(Network in Network)

    1×1卷积,又称为Network in Network 如果卷积的输出输入都只是一个平面,那么1x1卷积核并没有什么意义,它是完全不考虑像素与周边其他像素关系. 但卷积的输出输入是长方体,所以1x1卷 ...