Algoritma terbaik yang dikenal adalah untuk mengekspresikan faktorial sebagai produk kekuatan utama. Seseorang dapat dengan cepat menentukan bilangan prima serta kekuatan yang tepat untuk setiap bilangan prima menggunakan pendekatan saringan. Komputasi setiap daya dapat dilakukan secara efisien menggunakan kuadrat ulang, dan kemudian faktor-faktor tersebut dikalikan bersama. Ini dijelaskan oleh Peter B. Borwein, On Complexity of Calculating Factorials , Journal of Algorithms 6 376-380, 1985. ( PDF ) Singkatnya, dapat dihitung dalam waktu O ( n ( log n ) 3 log log n ) , dibandingkan dengan Ω ( nn !O ( n ( logn )3loglogn ) waktu yang diperlukan saat menggunakan definisi.Ω ( n2logn )
Apa yang mungkin dimaksud buku teks adalah metode membagi dan menaklukkan. Satu dapat mengurangi perkalian dengan menggunakan pola reguler dari produk.n - 1
Biarkan menyatakan 1 ⋅ 3 ⋅ 5 ⋯ ( 2 n - 1 ) sebagai notasi yang mudah. Susun ulang faktor-faktor ( 2 n ) ! = 1 ⋅ 2 ⋅ 3 ⋯ ( 2 n ) sebagai
( 2 n ) ! = n ! ⋅ 2 n ⋅ 3 ⋅ 5 ⋅ 7 ⋯ ( 2 n -n ?1 ⋅ 3 ⋅ 5 ⋯ ( 2 n - 1 )( 2 n ) ! = 1 ⋅ 2 ⋅ 3 ⋯ ( 2 n )
Sekarang anggaplah n = 2 k untuk beberapa bilangan bulat k > 0 . (Ini adalah asumsi yang berguna untuk menghindari komplikasi dalam diskusi berikut, dan idenya dapat diperluas ke n umum.) Kemudian ( 2 k ) ! = ( 2 k - 1 ) ! 2 2 k - 1 ( 2 k - 1 ) ? dan dengan memperluas pengulangan ini,
( 2 k ) ! =
( 2 n ) ! = n ! ⋅ 2n⋅ 3 ⋅ 5 ⋅ 7 ⋯ ( 2 n - 1 ) .
n = 2kk > 0n( 2k) ! = ( 2k - 1) ! 22k - 1( 2k - 1) ?
Komputasi
(2k-dan mengalikan produk parsial pada setiap tahap membutuhkan
(k-2)+2k-1( 2k) ! = ( 22k - 1+ 2k - 2+ ⋯ + 20) ∏i = 0k - 1( 2saya) ? = ( 22k- 1) ∏i = 1k - 1( 2saya) ? .
( 2k - 1) ? perkalian. Ini merupakan peningkatan faktor hampir
2 dari
2 k - 2 perkalian hanya menggunakan definisi. Beberapa operasi tambahan diperlukan untuk menghitung kekuatan
2 , tetapi dalam aritmatika biner ini dapat dilakukan dengan murah (tergantung pada apa yang dibutuhkan secara tepat, mungkin hanya perlu menambahkan akhiran
2 k - 1 nol).
( k - 2 ) + 2k - 1- 222k- 222k- 1
Kode Ruby berikut mengimplementasikan versi yang disederhanakan ini. Ini tidak menghindari menghitung ulang bahkan di mana ia bisa melakukannya: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)
Bahkan kode first-pass ini meningkat pada hal yang sepele
f = 1; (1..32768).map{ |i| f *= i }; print f
sekitar 20% dalam pengujian saya.
Dengan sedikit kerja, ini dapat ditingkatkan lebih lanjut, juga menghilangkan persyaratan bahwa menjadi kekuatan 2 (lihat diskusi ekstensif ).n2
Ingatlah bahwa fungsi faktorial tumbuh begitu cepat sehingga Anda membutuhkan bilangan bulat berukuran sembarang untuk mendapatkan manfaat dari teknik yang lebih efisien daripada pendekatan naif. Faktorial 21 sudah terlalu besar untuk bisa dimasukkan dalam 64-bit
unsigned long long int
.Namun, urutan di mana Anda melakukan perkalian penting. Perkalian pada integer mesin adalah operasi dasar yang membutuhkan waktu yang sama tidak peduli berapa nilai integer tersebut. Tetapi untuk bilangan bulat berukuran sewenang-wenang, waktu yang diperlukan untuk mengalikan a dan b tergantung pada ukuranΘ ( | a | ⋅ | b | ) | x | x Ω ( | a | + | b | ) maks ( | a | , | b | )
Berbekal latar belakang ini, artikel Wikipedia harus masuk akal.
Karena kompleksitas penggandaan bergantung pada ukuran bilangan bulat yang sedang dikalikan, Anda dapat menghemat waktu dengan mengatur perkalian dalam urutan yang membuat angka dikalikan kecil. Ini bekerja lebih baik jika Anda mengatur agar jumlahnya kira-kira berukuran sama. "Pembagian dalam setengah" yang mengacu pada buku teks Anda terdiri dari pendekatan pembagian dan penakluk berikut untuk melipatgandakan serangkaian bilangan bulat:
Lihat manual GMP untuk lebih spesifik.
sumber
Selain itu, algoritma iteratif dan rekursif Anda setara (hingga kesalahan floating point), karena Anda menggunakan rekursi ekor.
sumber