在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

比如python 默認的遞歸深度為1000, 所以如果直接執行func(1000)時,會抛出棧溢出的異常
def func(n):
if n==1:
return 1;
return n*func(n-1)
>>> func(10)
3628800
>>> func(1000)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in func
File "<stdin>", line 4, in func
File "<stdin>", line 4, in func
[Previous line repeated 995 more times]
File "<stdin>", line 2, in func
RecursionError: maximum recursion depth exceeded in comparison
遇到這種情況, 可以用sys.setrecursionlimit(10000) 臨時調整python棧大小,
另外可以解决递归调用栈溢出的方法是通过尾递归优化,尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
def func(n):
return func_inter(n,1)
def func_inter(m, n):
if m=1:
return n;
return func_inter(m-1, m*n)
遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的函数改成尾递归方式,也会导致栈溢出。但可以增加函數裝飾器來實現针对尾递归做优化。
class TailRecurseException(BaseException):
def __init__(self, args, kwargs):
self.args = args
self.kwargs = kwargs
def tail_call_optimized(g):
"""
This function decorates a function with tail call
optimization. It does this by throwing an exception
if it is it's own grandparent, and catching such
exceptions to fake the tail call optimization.
This function fails if the decorated5
function recurses in a non-tail context.
"""
def func(*args, **kwargs):
f = sys._getframe()
if f.f_back and f.f_back.f_back and f.f_back.f_back.f_code == f.f_code:
raise TailRecurseException(args, kwargs)
else:
while 1:
try:
return g(*args, **kwargs)
except TailRecurseException as e:
args = e.args
kwargs = e.kwargs
func.__doc__ = g.__doc__
return func
f = sys._getframe()实际上返回一个frameobject,简称f
f中的 f.f_back实际上会返回调用栈下一个元素的对象,也就是目前元素的上一个进栈对象
f.f_back.f_back顾名思义就是上上一个
f_code指的是目前代码路径
這種復用方式也是返回值不能包含表達式的原因
小结
使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。
针对尾递归优化的语言可以通过尾递归防止栈溢出。尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。
Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。
練習
斐波那契數列的尾递归
@tail_call_optimized
def fib(i, current = 0, next = 1):
if i == 0:
return current
else:
return fib(i - 1, next, current + next)
汉诺塔的移动。
请编写move(n, a, b, c)函数,它接收参数n,表示3个柱子A、B、C中第1个柱子A的盘子数量,然后打印出把所有盘子从A借助B移动到C的方法
def move(n, a, b, c):
if n==1:
print('a-->c')
move(n-1, a, c, b)
move(1, a, c, b)
move(n-1, b, a, c)
計算下列函數的返回值
def func1(m, n):
if m==0;
return n+1;
else:
if n==0:
return func1(m-1, 1);
else:
return func1(m-1, func1(m, n-1))
Reference
本文介绍了递归函数的概念,优点是定义简单,逻辑清晰,但可能导致栈溢出问题。递归深度超过限制会引发RecursionError。解决栈溢出可以尝试调整Python的递归限制或使用尾递归,但Python解释器未对尾递归优化。文章还讨论了尾递归优化的原理,并指出Python中防止栈溢出的其他方法。最后,提出了练习题目涉及斐波那契数列和汉诺塔问题。

123

被折叠的 条评论
为什么被折叠?



