Najlepszym znanym algorytmem jest wyrażanie silni jako iloczynu sił pierwszych. Można szybko określić liczby pierwsze, a także odpowiednią moc dla każdej liczby pierwszej, stosując podejście sitowe. Obliczenie każdej mocy można wykonać skutecznie za pomocą wielokrotnego kwadratu, a następnie mnożymy czynniki razem. Zostało to opisane przez Petera B. Borweina, On the Complexity of Calculating Factorials , Journal of Algorytmy 6 376–380, 1985. ( PDF ) Krótko mówiąc, można obliczyć w czasie O ( n ( log n ) 3 log log n ) , w porównaniu do Ω ( nn!O(n(logn)3loglogn) czas wymagany podczas korzystania z definicji.Ω(n2logn)
Podręcznik mógł chyba oznaczać metodę „dziel i rządź”. Można zmniejszyć mnożenie , stosując regularny wzór produktu.n−1
Niech oznacz 1 ⋅ 3 ⋅ 5 ⋯ ( 2 n - 1 ) jako wygodny zapis. Zmień kolejność współczynników ( 2 n ) ! = 1 ⋅ 2 ⋅ 3 ⋯ ( 2 n ) jako
( 2 n ) ! = n ! ⋅ 2 n ⋅ 3 ⋅ 5 ⋅ 7 ⋯ ( 2 n -n?1⋅3⋅5⋯(2n−1)(2n)!=1⋅2⋅3⋯(2n)
Załóżmy teraz, że n =
(2n)!=n!⋅2n⋅3⋅5⋅7⋯(2n−1).
dla jakiejś liczby całkowitej
k > 0 . (Jest to użyteczne założenie, aby uniknąć komplikacji w poniższej dyskusji, a pomysł można rozszerzyć na ogólne
n .) Następnie
( 2 k ) ! = ( 2 k - 1 ) ! 2 2 k - 1 ( 2 k - 1 ) ? i poprzez rozszerzenie tego nawrotu
( 2 k ) ! =n = 2kk > 0n( 2k) ! = ( 2k - 1) ! 2)2)k - 1( 2k - 1) ?
Komputery
( 2 k - 1 )?(2k)!=(22k−1+2k−2+⋯+20)∏i=0k−1(2i)?=(22k−1)∏i=1k−1(2i)?.
(2k−1)? i mnożenie produktów cząstkowych na każdym etapie zajmuje
( k - 2 ) + 2k - 1- 2 krotności. Jest to poprawa współczynnika prawie
z mnożenia
2 k - 2 przy użyciu samej definicji. Niektóre dodatkowe operacje są wymagane do obliczenia potęgi
2 , ale w binarnej arytmetyce można to zrobić tanio (w zależności od tego, co dokładnie jest wymagane, może wymagać jedynie dodania przyrostka
2 k - 1 zer).
2)2)k- 22)2)k- 1
Poniższy kod Ruby implementuje uproszczoną wersję tego. Nie pozwala to uniknąć ponownego obliczanianawet tam, gdzie może to zrobić: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)
Nawet ten kod pierwszego przejścia poprawia banalność
f = 1; (1..32768).map{ |i| f *= i }; print f
o około 20% w moich testach.
Przy odrobinie pracy można to jeszcze ulepszyć, usuwając również wymóg, aby była potęgą 2 (patrz obszerna dyskusjan2) ).