一尘不染

有效地计算组合和排列

algorithm

我有一些代码可以计算排列和组合,并且我正在尝试使其更适合大量使用。

我发现了一种更好的置换算法,可以避免较大的中间结果,但是我仍然认为我可以对组合做得更好。

到目前为止,我已经提出了一种特殊情况来反映nCr的对称性,但是我仍然想找到一种更好的算法来避免调用factorial(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)

阅读 242

收藏
2020-07-28

共1个答案

一尘不染

如果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 <= n,您需要检查该值,如果不是,请交换它们。为了优化使用,如果r <n / 2,则r = 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)
2020-07-28