有效地计算组合和排列

时间:2010-01-19 19:52:04

标签: python algorithm math combinations permutation

我有一些代码来计算排列和组合,我正在尝试让它更适合大数字。

我找到了一个更好的排列算法,避免了大的中间结果,但我仍然认为我可以做更好的组合。

到目前为止,我已经提出了一个特殊情况来反映nCr的对称性,但我仍然希望找到一种更好的算法来避免调用阶乘(r),这是一个不必要的大中间结果。如果没有这种优化,最后一次doctest尝试计算阶乘(99000)需要太长时间。

有人能建议一种更有效的方法来计算组合吗?

from math import factorial

def product(iterable):
    prod = 1
    for n in iterable:
        prod *= n
    return prod

def npr(n, r):
    """
    Calculate the number of ordered permutations of r items taken from a
    population of size n.

    >>> npr(3, 2)
    6
    >>> npr(100, 20)
    1303995018204712451095685346159820800000
    """
    assert 0 <= r <= n
    return product(range(n - r + 1, n + 1))

def ncr(n, r):
    """
    Calculate the number of unordered combinations of r items taken from a
    population of size n.

    >>> ncr(3, 2)
    3
    >>> ncr(100, 20)
    535983370403809682970
    >>> ncr(100000, 1000) == ncr(100000, 99000)
    True
    """
    assert 0 <= r <= n
    if r > n // 2:
        r = n - r
    return npr(n, r) // factorial(r)

13 个答案:

答案 0 :(得分:23)

如果n离r不远,那么使用组合的递归定义可能更好,因为xC0 == 1你将只有几次迭代:

这里的相关递归定义是:

nCr =(n-1)C(r-1)* n / r

使用尾递归可以使用以下列表很好地计算:

[(n - r,0),(n - r + 1,1),(n - r + 2,2),...,(n - 1,r - 1),(n,r )]

当然很容易在Python中生成(我们省略了自nC0 = 1以来的第一个条目)izip(xrange(n - r + 1, n+1), xrange(1, r+1))注意这假定r&lt; = n你需要检查它并交换它们,如果它们不是。如果r

现在我们只需要使用带有reduce的尾递归来应用递归步骤。我们从1开始,因为nC0是1,然后将当前值乘以列表中的下一个条目,如下所示。

from itertools import izip

reduce(lambda x, y: x * y[0] / y[1], izip(xrange(n - r + 1, n+1), xrange(1, r+1)), 1)

答案 1 :(得分:16)

两个相当简单的建议:

  1. 为避免溢出,请在日志空间中执行所有操作。使用log(a * b)= log(a)+ log(b)和log(a / b)= log(a) - log(b)这一事实。这使得使用非常大的因子很容易:log(n!/ m!)= log(n!) - log(m!)等。

  2. 使用伽玛函数代替阶乘。您可以在scipy.stats.loggamma中找到一个。这是一种比直接求和更有效的计算对数因子的方法。 loggamma(n) == log(factorial(n - 1)),同样地,gamma(n) == factorial(n - 1)

答案 2 :(得分:6)

如果您不需要纯python解决方案,gmpy2可能会有所帮助(gmpy2.comb非常快)。

答案 3 :(得分:6)

在scipy中有一个功能尚未提及:scipy.special.comb。基于doctest的一些快速计时结果(comb(100000, 1000, 1) == comb(100000, 99000, 1)为~0.004秒),它似乎很有效。

[虽然这个具体问题似乎与算法有关,但问题is there a math ncr function in python被标记为重复...]

答案 4 :(得分:3)

如果您的问题不需要知道排列或组合的确切数量,那么您可以使用Stirling's approximation作为阶乘。

这会导致像这样的代码:

import math

def stirling(n):
    # http://en.wikipedia.org/wiki/Stirling%27s_approximation
    return math.sqrt(2*math.pi*n)*(n/math.e)**n

def npr(n,r):
    return (stirling(n)/stirling(n-r) if n>20 else
            math.factorial(n)/math.factorial(n-r))

def ncr(n,r):    
    return (stirling(n)/stirling(r)/stirling(n-r) if n>20 else
            math.factorial(n)/math.factorial(r)/math.factorial(n-r))

print(npr(3,2))
# 6
print(npr(100,20))
# 1.30426670868e+39
print(ncr(3,2))
# 3
print(ncr(100,20))
# 5.38333246453e+20

答案 5 :(得分:2)

如果你正在计算N选择K(这是我认为你正在用ncr做的),那么动态编程解决方案可能要快得多。这样可以避免阶乘,如果您想稍后使用,可以保留表格。

这是一个教学链接:

http://www.csc.liv.ac.uk/~ped/teachadmin/algor/dyprog.html

我不确定如何更好地解决你的第一个问题,抱歉。

编辑:这是模型。有一些非常热闹的一对一错误,所以它当然可以更加清晰。

import sys
n = int(sys.argv[1])+2#100
k = int(sys.argv[2])+1#20
table = [[0]*(n+2)]*(n+2)

for i in range(1,n):
    table[i][i] = 1
for i in range(1,n):
    for j in range(1,n-i):
        x = i+j
        if j == 1: table[x][j] = 1
        else: table[x][j] = table[x-1][j-1] + table[x-1][j]

print table[n][k]

答案 6 :(得分:2)

from scipy import misc
misc.comb(n, k)

应该允许你计算组合

答案 7 :(得分:2)

对于3.7之前的Python:

def prod(
        items,
        start=1):
    for item in items:
        start *= item
    return start


def perm(n, k):
    if k > n or k < 0 or n < 0:
        raise ValueError(
            'Values must be non-negative and n >= k in perm(n, k)')
    else:
        return prod(range(n - k + 1, n + 1))


def comb(n, k):
    if k > n or k < 0 or n < 0:
        raise ValueError(
            'Values must be non-negative and n >= k in comb(n, k)')
    elif k > n - k:
        # return perm(n, k) // math.factorial(n - k)
        return math.factorial(n) // math.factorial(k) // math.factorial(n - k)
    else:
        # return perm(n, n - k) // math.factorial(k)
        return math.factorial(n) // math.factorial(n - k) // math.factorial(k)

对于python 3.8 +:

答案 8 :(得分:1)

更有效的nCr解决方案 - 空间明智和精确明智。

中间人(res)保证始终为int且永远不会大于结果。空间复杂度为O(1)(无列表,无拉链,无堆栈),时间复杂度为O(r) - 恰好是r乘法和r除法。

def ncr(n, r):
    r = min(r, n-r)
    if r == 0: return 1
    res = 1
    for k in range(1,r+1):
        res = res*(n-k+1)/k
    return res

答案 9 :(得分:1)

from numpy import prod

def nCr(n,r):
    numerator = range(n, max(n-r,r),-1)
    denominator = range(1, min(n-r,r) +1,1)
    return int(prod(numerator)/prod(denominator))

答案 10 :(得分:0)

使用xrange()代替range()会稍微加快速度,因为没有创建,填充,迭代,然后销毁中间列表。此外,reduce()operator.mul

答案 11 :(得分:0)

对于N选择K,您可以使用Pascals三角形。基本上你需要保持大小为N的数组来计算所有N个选择K值。只需要添加。

答案 12 :(得分:0)

您可以输入两个整数并导入数学库以查找阶乘,然后应用nCr公式

import math
n,r=[int(_)for _ in raw_input().split()]
f=math.factorial
print f(n)/f(r)/f(n-r)