Answers:
已知的最佳算法是将阶乘表示为素数乘积。可以使用筛子方法快速确定素数以及每个素数的合适功率。使用重复平方运算可以有效地计算每个功率,然后将这些系数相乘。这是由Peter B. Borwein描述,在复杂计算阶乘,算法杂志6 376-380,1985.(PDF)总之,可以用O (n (log n )3 log log n )时间来计算,与Ω (n使用定义时需要 2 log n )时间。
教科书可能意味着分治法。通过使用乘积的规则模式,可以减少乘法。
让分别表示1 ⋅ 3 ⋅ 5 ⋯ (2 Ñ - 1 )作为一个方便的符号。重新排列(2 n )的因数!= 1 ⋅ 2 ⋅ 3 ⋯ (2 Ñ )如 (2 Ñ )!= n !⋅ 2 Ñ ⋅ 3 ⋅ 5 ⋅ 7 ⋯ (2 Ñ - 现在假设对于某些整数 k > 0,n = 2 k。(这是一个有用的假设,可以避免在下面的讨论中引起麻烦,并且可以将其扩展到一般 n。)然后(2 k)!= (2 k − 1)!2 2 k - 1(2 k - 1)?并通过扩展此递归 (2 k)!=
def oddprod(l,h)
p = 1
ml = (l%2>0) ? l : (l+1)
mh = (h%2>0) ? h : (h-1)
while ml <= mh do
p = p * ml
ml = ml + 2
end
p
end
def fact(k)
f = 1
for i in 1..k-1
f *= oddprod(3, 2 ** (i + 1) - 1)
end
2 ** (2 ** k - 1) * f
end
print fact(15)
即使是此首过代码,其琐碎的改进
f = 1; (1..32768).map{ |i| f *= i }; print f
在我的测试中大约增加了20%
请记住,阶乘函数增长如此之快,以至于您需要任意大小的整数才能获得比朴素方法更有效的技术的任何好处。21的阶乘已经太大而无法容纳64位unsigned long long int
。
有了这样的背景,维基百科的文章应该是有道理的。
由于乘法的复杂性取决于要乘法的整数的大小,因此可以通过按顺序排列乘法以节省数字的时间来节省时间。如果您将数字安排为大致相同的大小,效果会更好。教科书所指的“二等分”由以下分治法相乘,以(整数)组整数相乘:
有关更多详细信息,请参见GMP手册。
甚至有更快的方法,不仅可以重新排列因子到还可以通过将数字分解为素数分解并重新排列所产生的非常小的整数的长乘积来拆分数字。我只是引用Wikipedia文章中的引用:Peter Borwein的“论计算阶乘的复杂性”和Peter Luschny的实现。n
¹ 有计算的更快的方法近似的,但这不再是在计算阶乘,而是在计算它的近似值。
由于阶乘函数增长如此之快,因此您的计算机只能存储对于相对较小的。例如,双精度型最多可以存储值。因此,如果您想要一种非常快速的计算算法,只需使用大小为的表。ñ 171 !n !171
如果您对或函数(或)感兴趣,这个问题将变得更加有趣。在所有这些情况下(包括),我都不太理解您教科书中的注释。Γ 日志Γ ñ !
顺便说一句,您的迭代和递归算法是等效的(直到浮点错误),因为您使用的是尾递归。