Найкращий алгоритм, який відомий - це виражати факторіал як продукт первинних сил. За допомогою ситового підходу можна швидко визначити праймери, а також правильну потужність для кожного праймера. Обчислення кожної потужності можна зробити ефективно, використовуючи повторне квадратування, і тоді множники множать разом. Це описав Пітер Б. Борвейн, Про складність обчислення факторів , Журнал алгоритмів 6 376–380, 1985. ( PDF ) Коротше кажучи, n!можна обчислити за час O(n(logn)3loglogn) , порівняно з Ω(n2logn) час, необхідний при використанні визначення.
Можливо, підручник мав на увазі метод ділення і перемоги. Можна зменшити множення n−1 , використовуючи звичайний зразок продукту.
Нехай позначають 1 ⋅ 3 ⋅ 5 ⋯ ( 2 n - 1 ) як зручне позначення. Переставіть коефіцієнти ( 2 n ) ! = 1 ⋅ 2 ⋅ 3 ⋯ ( 2 n ) як
( 2 n ) ! = n ! ⋅ 2 n ⋅ 3 ⋅ 5 ⋅ 7 ⋯ ( 2 n -n?1⋅3⋅5⋯(2n−1)(2n)!=1⋅2⋅3⋯(2n)
Тепер припустимо, що n = 2 k для деякого цілого числа k > 0 . (Це корисне припущення, щоб уникнути ускладнень у наступному обговоренні, і ідею можна поширити на загальну п .) Тоді ( 2 к ) ! = ( 2 к - 1 ) ! 2 2 к - 1 ( 2 к - 1 ) ? і розширивши цю повторюваність,
( 2 к ) ! =
(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)?а множення часткових добутків на кожному етапі займає
множення. Це поліпшення коефіцієнта майже
2 від
2 k - 2 множення лише за допомогою визначення. Для обчислення потужності
2 потрібні деякі додаткові операції , але у двійковій арифметиці це можна зробити дешево (залежно від того, що саме потрібно, може знадобитися просто додавання суфікса
2 к - 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% в моєму тестуванні.
Додавши трохи роботи, це можна вдосконалити ще більше, також усуваючи вимогу, щоб була потужністю 2 (див. Широке обговорення ).n2