整数阶乘组合计算python_关于python:有效地计算组合和排列

本文探讨了如何在Python中优化计算整数阶乘、排列(npr)和组合(ncr)的方法,以处理大量数据。作者分享了现有算法,并寻求更高效的组合计算策略,避免大中间结果导致的效率问题。提出了使用尾递归和动态编程等方法,并讨论了日志空间计算和伽马函数等优化技巧。
摘要由CSDN通过智能技术生成

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

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

到目前为止,我已经提出了一种特殊情况来反映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相同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值