【问题标题】:How to do recursion with more than limit digit?如何进行超过限制位数的递归?
【发布时间】:2020-04-14 02:15:52
【问题描述】:

我想对下面的程序进行递归,但由于输入的数字太大(19 位)。它在我的程序中引发错误 MemoryError:堆栈溢出RecursionError:比较中超出了最大递归深度。我尝试使用

重置递归限制
import sys
sys.setrecursionlimit(100000)

但这无济于事。你有什么解决方案来处理这么大的数字吗? 这是我的整个程序。

def recursion(n):
    return 1 if n < 2 else n * recursion(n - 2)


string = str(recursion(1000000000000000000))

【问题讨论】:

  • 有时递归不是一个有用的方法。
  • 你对我在递归之外使用的其他方法有什么建议吗?
  • 如果您遇到内存错误并且您的实现是正确的,这几乎肯定意味着您需要更改为非递归方法。 10^19 的递归深度在任何现有计算机上都是不可能的
  • 即使不考虑递归限制问题,我怀疑你是否有足够的内存来保存str(recursion(1000000000000000000)) 或计算recursion(1000000000000000000) 的时间。使用对数,您可以验证所涉及的内存大到无法实现。您尝试计算的函数的增长速度比任何指数函数都

标签: python python-3.x recursion


【解决方案1】:

很遗憾python doesn't optimize tail recursion 进行迭代。但你可以自己做:

def recursion(n):
    result = 1
    for i in range(2, n+1, 2):
        result *= i
    return result

如果你真的希望它是递归的,那么你唯一能做的就是增加递归限制和堆栈大小,但这不是一个好主意。

【讨论】:

  • 谢谢。我试过你的方法,但奇怪的是结果总是1。你知道为什么吗?再加上程序永远不会结束。它没有显示任何输出。
  • @Vincent.N 这个答案中的代码并不总是返回 1。如果这是你观察到的——也许检查你的缩进。
  • 它“永远”不会结束,因为循环 1000000000000000000 次需要很多时间。在 for 循环中添加一个简单的 print 以查看它的效果。
  • 谢谢大家的回答。 @JohnColeman 抱歉,这对我来说太愚蠢了。我把 i 和 1 弄混了,没有注意到。
  • @RafalS 我试过了。这是很多数字,超出了我对运行时间的预期。所以我猜这些方法还不够有效。
【解决方案2】:

这是递归without tail call optimization的实际限制之一。

Python 对递归调用的数量进行了防范,以避免堆栈溢出。这是您看到的RecursionError

Python 3.7.4 (default, Aug 13 2019, 20:35:49) 
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def recursion(n):
...     return 1 if n < 2 else n * recursion(n - 2)
... 
>>> 
>>> string = str(recursion(1000000000000000000))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in recursion
  File "<stdin>", line 2, in recursion
  File "<stdin>", line 2, in recursion
  [Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded in comparison

增加是一种可能的解决方案,但它只会删除内置的防护。如果进行了足够多的递归调用,您最终将耗尽内存,这是您遇到的第二个错误(MemoryError: Stack overflow 或在我的情况下是分段错误)。

Python 3.7.4 (default, Aug 13 2019, 20:35:49) 
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.setrecursionlimit(100000)
>>> def recursion(n):
...     return 1 if n < 2 else n * recursion(n - 2)
... 
>>> string = str(recursion(1000000000000000000))
Segmentation fault

在 Python 中解决这个问题的惯用方法是使用迭代:

def iteration(n):
    result = 1
    for i in range(n, 1, -2):
        result *= i
    return result

使用您的原始输入 (1e18) 运行此示例不会很快终止。随着整数的增长,计算所需的时间越来越多,因为这些操作需要使用大于 64 位的整数表示(或者您的 CPU 能够用单个寄存器表示的位数)。

尽管如此,这将是解决这个问题的 Pythonic 解决方案。

【讨论】:

    【解决方案3】:

    即使是n,您的函数也会计算2*4*6* ... *n。很容易得出这与2^(n/2) * (n/2)! 相同。例如,2*4*6*8*10 = (2*1)*(2*2)*(2*3)*(2*4)*(2*5) 因此是2^5*(1*2*3*4*5) = 32*5!。这可以通过函数快速有效地计算:

    import math
    
    def f(n):
        k = n//2
        return 2**k * math.factorial(k)
    

    对于奇数n,它稍微复杂一些。请参阅来自 Math Overflow 的 this question

    通过Stirling's approximationf(n) 渐近等价于sqrt(pi*n)*(n/e)^(n/2)。插入n = 10^18,并获取结果的base-2 日志,您可以看到f(10^18) 大约需要3.6 exabytes 来存储结果整数(这可能超出您的计算机可以处理的范围)。

    【讨论】:

      猜你喜欢
      • 2015-05-20
      • 2021-12-28
      • 1970-01-01
      • 2014-08-04
      • 2017-02-02
      • 2022-01-21
      • 1970-01-01
      • 1970-01-01
      • 2019-03-12
      相关资源
      最近更新 更多