python 递归
递归算是一直对我来说比较致命的一点,一直没有办法非常通透的理解。最近在业务上有一段代码几乎想不到非递归的场景。所以在这次加深了对递归的认识。
python 递归的限制
首先递归第一个要考虑的就是什么时候结束。结果条件没写好就会导致调用栈太深,甚至爆栈的危险。python在这里做了最大递归层数的限制。
sys.getrecursionlimit()
一般设置为1000。 当然如果你非常有信息可以把值设置得更大一些。通过相应的set 方法。但是这只是对层数的限制,相应的栈大小没有变。还是可能会爆栈,所以还是应该谨慎使用。(目前认为 栈的限制在于对线程栈大小限制不区分语言 ulimit 可以查看).
深度优先搜索
关于深度优先搜索,对于树而言他的递归其实是可以预知的,那就是树的深度。(当然,前提是你知道树的深度)在这种情况下基本不用考虑爆栈的危险。为什么使用深度递归搜索呢?因为某些时候把递归转为迭代太耗费脑子了。
如这一段代码,意义在于同步json和DB之间的数据。如果json dict有不同则新插入数据库。我们会发现在传入到新的函数是会新的值,也就是有状态的转移。而对于非递归的时候几乎只能够说是遍历,而无法很好的修改参数以及恢复参数。
def location_dfs(tree_node, node_name, parentid=0, level=0):
location = get_location(name=node_name, parentid=parentid, level=level)
if not location:
location = add_location(name=node_name, parentid=parentid, level=level)
log.data('regular_sync_location_tab| new location info: id:%s parentid:%s name:%s level:%s',
location.id, location.parentid, location.name, location.level)
print 'add location id:%s name:%s parentid:%s' % (location.id, location.name, location.parentid)
add_notify_list(location)
for key in tree_node:
if isinstance(tree_node, dict):
next_subtree = tree_node[key]
else:
next_subtree = {}
location_dfs(next_subtree, key, parentid=location.id, level=level + 1)
一开始想看到最末尾也是调用其自身,让我想起了尾递归优化,仔细一看使其不然,代码是在for循环是调用前自身,所以不是尾递归。尾递归应该可以改为return xxx的形式。
尾递归优化
尾递归优化定义:尾调用的概念非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。
一下不属于尾递归
// 情况一
function f(x){
let y = g(x);
return y;
}
// 情况二
function f(x){
return g(x) + 1;
}
以下属于尾递归
function factorial (num, total) {
if (num === 1) return total;
return factorial(num - 1, num * total);
}
针对这种情景很多编译器会进行优化。python 编译器没有优化功能,但是有一个装饰器可以实现这个功能,也能让我们更清楚尾递归到底是如何优化的。
import sys
class TailCallException:
def __init__(self, args, kwargs):
self.args = args
self.kwargs = kwargs
def tail_call_optimized(func):
def _wrapper(*args, **kwargs):
f = sys._getframe()
if f.f_back and f.f_back.f_back and f.f_code == f.f_back.f_back.f_code:
raise TailCallException(args, kwargs)
else:
while True:
try:
return func(*args, **kwargs)
except TailCallException, e:
args = e.args
kwargs = e.kwargs
return _wrapper
@tail_call_optimized
def fib(n, a, b):
if n == 1:
return a
else:
return fib(n-1, b, a+b)
r = fib(1200, 0, 1) #突破了调用栈的深度限制
其核心思想就是:调用栈函数内容是一致的。当检查到3个一样的栈时,就会抛出异常,把参数带上,被上一层的fib捕获,这时候栈已经收缩。while True 内部继续调用,程序继续运行。最终的效果是递归栈的深度不超过3.
参考:尾递归优化