最终递归到996次停止了递归,也就是python的递归深度限制在了1000附近。
1000层的限制已经是足够的了,但是还是有可能限制到某些计算,所以python提供了一个修改限制的方
import sys def limitless(n): print('第' + str(n) + '次调用') n += 1 return limitless(n) print(sys.getrecursionlimit())#1000 sys.setrecursionlimit(2000) limitless(1)
第1994次调用
第1995次调用
第1996次调用
Traceback (most recent call last):
File “D:/github/Data-Structure/code/递归.py”, line 70, in limitless
return limitless(n)
File “D:/github/Data-Structure/code/递归.py”, line 70, in limitless
return limitless(n)
File “D:/github/Data-Structure/code/递归.py”, line 70, in limitless
return limitless(n)
[Previous line repeated 995 more times]
File “D:/github/Data-Structure/code/递归.py”, line 68, in limitless
print(‘第’ + str(n) + ‘次调用’)
RecursionError: maximum recursion depth exceeded while calling a Python objec
可见把这个深度该为2000后便多了1000次调用,但这个深度显然不是设置多少就是多少,毕竟还有计算机CPU与内存的限制,比如吧深度改为10000,那么运行后
第3920次调用
第3921次调用
第3922次调用
第3923次调用
Process finished with exit code -1073741571 (0xC00000FD)
到达3923次便终止了,查询-1073741571发现是递归栈溢出的问题。
尾递归
如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。
Python解释器在对于一次函数调用中,会使用一个栈帧来保存当前调用的函数的信息,如输入参数、返回值空间、计算表达式时用到的临时存储空间、函数调用时保存的状态信息以及输出参数。因此在递归的调用中,这种未执行完的函数会一层一层的占用大量的栈帧。如果将递归的调用放到函数执行的最后一步,那么执行完这步,该次函数的栈帧就会释放,调用函数的新栈帧就会替换掉之前的栈帧,所以无论调用的深度有多少次,都只会占用一个栈帧,那也就不会发生栈溢出的问题。这就是尾递归。
所以根据需要,尾递归必须是线性递归,并且递归调用的返回值必须立即返回。
拿一个阶乘递归函数举例
def factorial(n): """ 阶乘递归函数 """ if n == 0: return 1 else: return n * factorial(n - 1)
上边这个是一个普通的递归,下面把他改成尾递归的形式
def facttail(n, res): """ 阶乘尾递归,res初始为1 """ if n < 0: return 0 elif n == 0: return 1 elif n == 1: return res else: return facttail(n - 1, n *res)
这个函数比之前那个还多了个res,第一种每次调用完要乘n,这里的res就起了相同的作用,由于尾递归每一层的栈帧要释放,所以通过res来作为相乘的过程。我个人认为尾递归的难度就在于参数的设计,因为它的前提条件就是调用后什么也不再执行了,所以要作为传递的东西就得提前通过参数设计传递,总之要想设计一个尾递归的算法还是需要好好思考一下的。