我有一些代码可以计算排列和组合,并且我正在尝试使其更适合大量使用。
我找到了一种更好的置换算法,可以避免较大的中间结果,但我仍然认为我可以对组合做得更好。
到目前为止,我已经提出了一种特殊情况来反映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)
这是很久以前问过的,但是无论如何...我设计了一种算法,可以计算C(n,m)= n! /(m!(n-m)!),只要结果适合整数(可以很容易地是长整数)即可。 我用Java编写了它,但是将它翻译成Python或任何其他程序语言应该很容易:stackoverflow.com/questions/50292530/(查找combinations(int n, int m))
如果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 现在,我们只需要使用带有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)
对于单个nCr,这会更好,但是,如果您有多个nCr(N的数量级),那么动态编程方法会更好,尽管它的建立时间很长,因为除非有必要,否则它不会溢出到大数。
两个非常简单的建议:
为避免溢出,请在日志空间中执行所有操作。使用log(a * b)= log(a)+ log(b)和log(a / b)= log(a)-log(b)的事实。这样可以轻松处理非常大的阶乘:log(n!/ m!)= log(n!)-log(m!),等等。
使用伽马函数而不是阶乘。您可以在scipy.stats.loggamma中找到一个。与直接求和相比,这是一种计算对数阶乘的更为有效的方法。 loggamma(n) == log(factorial(n - 1)),以及类似的gamma(n) == factorial(n - 1)。
在日志空间中做事的好建议。尽管不确定"精确"的含义。使用log-floats是否会导致大数舍入错误?
@Gorgapor:我想说一个更清晰的方法是:"避免溢出"。编辑。
请注意,由于浮点数的精度有限,因此不会给出确切的结果。
@starblue:但是您知道真正的答案必须是整数,因此,如果您执行诸如round(exp(logFactorial(n)))之类的操作,则对小n而言将是准确的。对于大的n来说可能是不精确的,但是除了(慢)任意精度之外,任何其他事情都将是完全错误的。
对于小n而言,计算此问题不会有太多麻烦。关键是要针对大n精确地计算此值,并且Im已经使用任意精度,因为Im使用python long。
如何使用gamma和loggamma函数?都不返回整数,而是返回scipy.stats._distn_infrastructure.rv_frozen对象。
math.gamma和math.lgamma产生整数结果。尚不清楚scipy.stats函数在做什么。
scipy中有一个尚未提及的功能:scipy.special.comb。根据您的doctest的一些快速计时结果,它似乎很有效(comb(100000, 1000, 1) == comb(100000, 99000, 1)约为0.004秒)。
[虽然这个特定问题似乎与算法有关,但问题是在python中有一个数学ncr函数被标记为该函数的重复...]
如果您不需要纯Python解决方案,则gmpy2可能会有所帮助(gmpy2.comb非常快)。
感谢您的参考,多数民众赞成在一个很好的实际解决方案。不过,对我来说,这更像是一个学习项目,因此我对算法更感兴趣,而不是实际结果。
对于那些在写完此答案几年后才得出答案的人,gmpy现在被称为gmpy2。
如果您的问题不需要知道排列或组合的确切数目,则可以对阶乘使用斯特林近似。
这将导致如下代码:
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
阶乘的主要问题是结果的大小,而不是计算结果的时间。同样,这里结果的值比可用浮点值精确表示的要大得多。
from scipy import misc
misc.comb(n, k)
应该允许您计算组合
如果您要计算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]
看来这个实现是O(n ^ 2),而据我所知,我设计的尾部递归是O(n)。
似乎使用了不同的递归定义。这里n选择k = n-1选择k-1 + n-1选择k,而我使用n选择k = n-1选择k-1 * n / k
的确如此。我将很快编辑这篇文章,以包括该算法的快速python模拟。您的速度明显更快。如果Gorgapor拥有一些需要数小时才能完成乘法运算的奇异机器,我将在此处留下我的文章。 >。>
这可能是O(N ^ 2),但是它会预先计算所有nCr组合对,因此如果您要大量使用nCr并使用许多不同的值,这会更快,因为查找为O(1)并且不那么容易接受溢出。对于一个值,O(N)算法更好。
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
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))
您可以输入两个整数并导入数学库以找到阶乘,然后应用nCr公式
import math
n,r=[int(_)for _ in raw_input().split()]
f=math.factorial
print f(n)/f(r)/f(n-r)
对于N选择K,您可以使用帕斯卡三角形。基本上,您需要保持大小为N的数组来计算所有N个选择K值。仅需要添加。
这基本上是Agor建议的,但是它将是O(n ^ 2)。由于如今使用乘法和除法实际上不再是问题,因此使用不同的递归关系可以使算法达到我所描述的O(n)。
由于没有创建,填充,迭代并销毁任何中间列表,因此使用xrange()而不是range()可以稍微加快速度。此外,将reduce()与operator.mul一起使用。
抱歉,我不清楚,我的代码是python 3,而不是python2。python3中的范围与python 2中的xrange相同。