【问题标题】:Factorial of a large number in pythonpython中大数的阶乘
【发布时间】:2013-05-01 20:32:03
【问题描述】:

这是我处理阶乘的方法:

def factorial(n):
    '''Returns factorial of n'''
    r = 1
    for i in range(1, n + 1):
        r *= i
    return r

我认为这很简单,但我想你可以做一些更高效的事情,因为像 100000 这样的大数字需要很长时间。我的问题是,有吗? math.factorial() 也不好,它花费的时间大致相同。

【问题讨论】:

  • 使用 xrange 代替 range 至少可以节省一些内存,如果不是时间的话。 xrange 是一个生成器,不需要提前分配整个列表。
  • 谢谢,但我使用的是 Python 3,并且 range() 现在是一个生成器。
  • 你也想过使用内置的math.factorial吗?它比你上面的代码快一点。

标签: python algorithm factorial


【解决方案1】:

按顺序相乘,

r = 1
for i in range(1, n + 1):
    r *= i
return r

非常快速地创建一个大数(如数万位),然后你有很多一个大数和一个小数的乘法。至少有一个因子很大的乘法很慢。

例如,您可以通过减少涉及大量数字的乘法次数来大大加快速度

def range_prod(lo,hi):
    if lo+1 < hi:
        mid = (hi+lo)//2
        return range_prod(lo,mid) * range_prod(mid+1,hi)
    if lo == hi:
        return lo
    return lo*hi

def treefactorial(n):
    if n < 2:
        return 1
    return range_prod(1,n)

产生,计算100000! % 100019的时间(我第一次尝试len(str(fun(100000)),但转换为字符串的速度非常慢,所以差异看起来比实际小):

$ python factorial.py 
81430
math.factorial took 4.06193709373 seconds
81430
factorial took 3.84716391563 seconds
81430
treefactorial took 0.344486951828 seconds

所以100000! 的速度提高了 10 倍以上。

【讨论】:

  • 你同意在 BSD 许可的 SciPy 中使用它吗? github.com/scipy/scipy/pull/6020
  • @endolith 当然。不过,我不对代码中的错误承担任何责任。我也不能声称该算法有任何独创性,它已经过时了。
  • 所以这计算为 9!作为((2*3)*(4*5))*((6*7)*(8*9))?最好使用((2*9)*(3*8))*((4*7)*(5*6)) 让它们更小更长时间?
  • @endolith 就中间产品的大小而言,这并没有太大的区别。当我测试时(很久以前,在 Haskell 中),我无法让该方案比上述方案更快,因为代码变得更加复杂。做测试,它可能会给你带来一点好处,但不要期望太高。
  • 在 Python 3.7.8 中对于 1000000 似乎没有影响。
【解决方案2】:

因子变得非常大,因此处理数字的对数通常会更好。

许多语言都有一个 lgamma 库函数,用于计算 n-1 阶乘的自然对数。

这意味着您可以通过 lgamma(n+1) 计算阶乘 (n) 的自然对数。

您可以除以 log10 以将其转换为以 10 为底的对数。

所以如果你只想要位数,那么这段 Python 代码会立即给出答案:

from math import *
print ceil(lgamma(100000+1)/log(10))

【讨论】:

  • logamma 在 Python 2.7.x 和 Python 3.2+ 中(但不在 3.0.x 或 3.1.x 中)。
  • 有趣的事实:查看源代码,Python 使用 Lanczos 近似作为伽马函数 (Modules/mathmodules.c)。
【解决方案3】:

如果您需要较短的执行时间并且不需要尽可能高的准确度,您可以使用近似公式,例如Stirling approximation

【讨论】:

    【解决方案4】:

    如果您只需要一个近似值,Ramanujan 的阶乘近似值应该比斯特林的更准确。

    如果您需要(或想要)精确的东西,您可以尝试 GMP,即 GNU 多精度库。我已经成功地将它用于 Python 中大数的素性测试。

    【讨论】:

      【解决方案5】:

      您可以使用 reduce 函数而不是显式循环:

      >>> from functools import reduce
      >>> mul = int.__mul__
      >>> len(str(reduce(mul, range(2,100001), 1)))
      456574
      >>> 
      

      在 Python 2 中,您需要使用 long:long.__mul__len(str(reduce(mul, range(2L,100001L), 1L)))

      【讨论】:

        【解决方案6】:

        真正需要真正的阶乘值 n 实际上是不寻常的!在许多应用领域。通常使用阶乘的自然对数更为现实。我想不出任何不能将日志用作更好选择的应用程序,因为阶乘最常用于计算相关的值 选择事物组合的概率。

        一个常见的计算是基于阶乘的概率,例如选择二项式系数 (n k) = n! / (k!(n-k)!)。鉴于这是阶乘的比率,则 log(n k) = log(n!)-log(k!)-log((n-k)!) 可以使用各种对数阶乘近似值之一可靠地计算。而且,如果您进行大量概率数学运算,通常最好在对数域中进行(以分贝为单位测量概率),因为它通常涉及小于 1 的极宽范围的数字,因此使用常见的数学精度会很快崩溃如果不使用日志版本,则使用浮点表示。

        E.T.Jaynes 是一位著名的数学家和概率论专家,我推荐他的书“概率论:科学的逻辑”作为该主题和使用对数概率的贝叶斯推理和信息论的非常易读的资料。

        【讨论】:

        • 如果可以,您应该指向“对数阶乘近似值”的链接,因为它们与您对计算大数阶乘问题的答案最相关。
        • 杰恩斯的书序言的一些非常易读的页面在books.google.com/books?isbn=0521592712
        【解决方案7】:

        您确实意识到阶乘(100000)是aproximately 2.8242294080×10^456,573
        这就是为什么它很慢,它很大。

        【讨论】:

        • 嗯,我需要的是得到阶乘的长度,但在我能做到之前,我需要生成数字(或者至少我是这么认为的)。 len(str(factorial(100000))) 实际上在大约一分钟后返回 456574。
        【解决方案8】:

        您可以返回 gamma 函数 (math.gamma(x)),但使用 for 循环生成阶乘可能会更快

        【讨论】:

        • math.gamma(100000) 不幸引发了OverflowError;该函数仅适用于最大约 170 的值。
        【解决方案9】:

        二次效应导致的减速:随着 n 变大,您必须进行更多的乘法运算,但也必须乘以更大的数字。

        找到更好的算法并非易事。您可以尝试利用对称性(如在 FFT 中)。以不同的顺序进行乘法运算也很划算,结果是中间结果,这样你最终只能乘以几个非常大的数字,但我还没想到最后。在任何情况下,您都必须找到一个法律来加以利用。

        查看here 以获得更多灵感。

        【讨论】:

        • “以不同的顺序进行乘法运算也很划算,结果是中间结果,这样你最终只能乘以几个非常大的数字”我试过了,在Haskell 我从中获得了 的加速,但我在 Python 中的尝试并没有产生很大的不同。不知道为什么。
        • Duh! 这不是算法,而是转换为字符串的速度太慢了!
        猜你喜欢
        • 2022-01-18
        • 2011-07-05
        • 1970-01-01
        • 2011-10-17
        • 1970-01-01
        • 2020-05-29
        • 2016-01-04
        • 1970-01-01
        相关资源
        最近更新 更多