Лучший известный алгоритм состоит в том, чтобы выразить факториал как произведение простых степеней. Можно быстро определить простые числа, а также правильную мощность для каждого простого числа, используя метод сита. Вычисление каждой мощности может быть эффективно выполнено с использованием повторного возведения в квадрат, а затем коэффициенты умножаются вместе. Это было описано Питером Б. Borwein, О сложности вычисления факториалов , журнал алгоритмов 6 376-380, 1985. ( PDF ) Короче говоря, можно вычислить за время O ( n ( log n ) 3 log log n ) по сравнению с Ω ( nn!O(n(logn)3loglogn) время, необходимое при использовании определения.Ω(n2logn)
Возможно, учебник имел в виду метод «разделяй и властвуй». Можно уменьшить умножения, используя регулярный образец продукта.n−1
Пусть обозначают 1 ⋅ 3 ⋅ 5 ⋯ ( 2 п - 1 ) в качестве удобного обозначения. Переставь множители ( 2 n ) ! = 1 ⋅ 2 ⋅ 3 ⋯ ( 2 n ) при
( 2 n ) ! = п ! ⋅ 2 н ⋅ 3 ⋅ 5 ⋅ 7 ⋯ ( 2 н -n?1⋅3⋅5⋯(2n−1)(2n)!=1⋅2⋅3⋯(2n)
Теперь предположим, что n = 2 k для некоторого целого числа k > 0 . (Это полезное предположение, чтобы избежать осложнений в следующем обсуждении, и эту идею можно распространить на общее п .) Тогда ( 2 k ) ! = ( 2 k - 1 ) ! 2 2 k - 1 ( 2 k - 1 ) ? и расширив это повторение,
( 2 k ) ! знак равно
(2n)!=n!⋅2n⋅3⋅5⋅7⋯(2n−1).
n=2kk>0n(2k)!=(2k−1)!22k−1(2k−1)?
Вычислительный
( 2 к - 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
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% в моем тестировании.
Если немного поработать, это можно еще улучшить, также устраняя требование N быть силой 2(см. обширное обсуждение ).