递归函数的危险

时间:2010-11-25 14:53:16

标签: python recursion

通常有人说不建议在python中使用递归函数(递归深度限制,内存消耗等)

我从this question获取了一个排列示例。

def all_perms(str):
  if len(str) <=1:
    yield str
  else:
    for perm in all_perms(str[1:]):
        for i in range(len(perm)+1):
            yield perm[:i] + str[0:1] + perm[i:]

之后我把它变成了一个非递归版本(我是一个python新手)

def not_recursive(string):
  perm = [string[0]]
  for e in string[1:]:
    perm_next = []
    for p in perm:
        perm_next.extend(p[:i] + e + p[i:] for i in range(len(p) + 1))
    perm = perm_next

  for p in perm:
    yield p

我比较了他们

before=time()
print len([p for p in all_perms("1234567890")])
print "time of all_perms %i " % (time()-before)

before=time()
print len([p for p in not_recursive("1234567890")])
print "time of not_recursive %i " % (time()-before)

before=time()
print len([p for p in itertools.permutations("1234567890")])
print "time of itertools.permutations %i " % (time()-before)

结果非常有趣。递归函数最快5秒,然后递归8秒,然后建立35秒。

Python中的递归函数 坏吗?内置的itertools.permutations有什么问题?

4 个答案:

答案 0 :(得分:8)

递归是,适用于干净,清晰,递归实现的问题。但与所有编程一样,您必须执行一些算法分析才能理解性能特征。在递归的情况下,除了操作次数之外,还必须估计最大堆栈深度。

当新程序员认为递归是神奇的并且没有意识到底层有堆栈使得它成为可能时,大多数问题都会发生。新的程序员也已经知道分配内存并且从不释放它和其他错误,所以递归并不是唯一的危险。

答案 1 :(得分:6)

递归在Python中是“坏的”,因为它通常比迭代解决方案慢,并且因为Python的堆栈深度不是无限的(并且没有尾调用优化)。对于求和函数,是的,您可能想要无限深度,因为想要对一百万个数字的列表求和是完全合理的,并且性能增量将成为如此大量项目的问题。在这种情况下,你不应该使用递归。

另一方面,如果您正在从XML文件读取DOM树,则不太可能超过Python的递归深度(默认为1000)。它肯定可以,但实际上,它可能不会。当您知道提前使用哪种数据时,您可以确信不会溢出堆栈。

在我看来,递归树行走比写入和读取更自然,并且递归开销通常只是运行时间的一小部分。如果真的对你来说需要16秒而不是14秒,那么扔PyPy可能会更好地利用你的时间。

递归似乎很适合您发布的问题,如果您认为代码更容易阅读和维护,并且性能足够,那就去吧。

我从小就在计算机上编写代码,作为一个实际问题,有限的递归深度大约为16,如果提供的话,那么1000对我来说似乎很奢侈。 : - )

答案 2 :(得分:2)

你的时间完全错误:

def perms1(str):
  if len(str) <=1:
    yield str
  else:
    for perm in perms1(str[1:]):
        for i in range(len(perm)+1):
            yield perm[:i] + str[0:1] + perm[i:]

def perms2(string):
  perm = [string[0]]
  for e in string[1:]:
    perm_next = []
    for p in perm:
        perm_next.extend(p[:i] + e + p[i:] for i in range(len(p) + 1))
    perm = perm_next

  for p in perm:
    yield p

s = "01235678"
import itertools
perms3 = itertools.permutations

现在用timeit测试它:

thc:~$ for i in 1 2 3; do echo "perms$i:"; python -m timeit -s "import permtest as p" "list(p.perms$i(p.s))"; done 
perms1:
10 loops, best of 3: 23.9 msec per loop
perms2:
10 loops, best of 3: 39.1 msec per loop
perms3:
100 loops, best of 3: 5.64 msec per loop

正如您所见,itertools.permutations是迄今为止最快的,其次是递归版本。

但纯Python函数都没有机会快速,因为它们执行昂贵的操作,例如添加列表ala perm[:i] + str[0:1] + perm[i:]

答案 3 :(得分:0)

我无法重现您的计时结果(在Mac OS X上的Python 2.6.1中):

>>> import itertools, timeit
>>> timeit.timeit('list(all_perms("0123456789"))', 
...               setup='from __main__ import all_perms'),
...               number=1)
2.603626012802124
>>> timeit.timeit('list(itertools.permutations("0123456789"))', number=1)
1.6111600399017334