知られている最良のアルゴリズムは、階乗を素数の累乗の積として表現することです。ふるいアプローチを使用して、素数と各素数の適切なパワーをすばやく決定できます。各電力の計算は、二乗の繰り返しを使用して効率的に実行でき、その後、係数が乗算されます。これは、Peter B. Borweinによる、階乗計算の複雑性について、Journal of Algorithms 6 376–380、1985 によって説明されました。(PDF)要するに、定義を使用するときに必要な時間と比較して、時間で計算できます。n!O(n(logn)3loglogn)Ω(n2logn)
教科書がおそらく意味するのは、分割統治法でしょう。積の通常のパターンを使用することにより、乗算を減らすことができます。n−1
してみましょう示すの便利な表記として。要因を並べ替え as
ここで、整数に対してと仮定します。(これは、以下の説明での複雑さを避けるための便利な仮定であり、一般的な拡張することができます。)その後そして、この繰り返しを拡大することにより、
コンピューティングn?1⋅3⋅5⋯(2n−1)(2n)!=1⋅2⋅3⋯(2n)
(2n)!=n!⋅2n⋅3⋅5⋅7⋯(2n−1).
n=2kk>0n(2k)!=(2k−1)!22k−1(2k−1)?(2k)!=(22k−1+2k−2+⋯+20)∏i=0k−1(2i)?=(22k−1)∏i=1k−1(2i)?.
(2k−1)?各段階で部分積を乗算するには、乗算が必要です。これは、定義を使用するだけで乗算からほぼ倍に改善されます。のべき乗を計算するにはいくつかの追加の操作が必要ですが、バイナリ算術ではこれを安価に行うことができます(正確に必要なものに応じて、ゼロのサフィックスを追加するだけでよい場合があります)。
(k−2)+2k−1−222k−222k−1
次のRubyコードは、この単純化されたバージョンを実装しています。これは再計算を避けませんそれができる場所でさえ:n?
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%でした。
少しの作業で、これはさらに改善され、がべき乗であるという要件もなくなります(詳細な説明を参照)。n2