Der beste Algorithmus, der bekannt ist, besteht darin, die Fakultät als Produkt der Primkräfte auszudrücken. Mit einem Siebansatz kann man schnell die Primzahlen sowie die richtige Leistung für jede Primzahl bestimmen. Die Berechnung jeder Potenz kann durch wiederholtes Quadrieren effizient durchgeführt werden, und dann werden die Faktoren miteinander multipliziert. Dies wurde von Peter B. Borwein, Über die Komplexität von Rechenfaktoren , Journal of Algorithms 6 376–380, 1985, beschrieben. ( PDF ) Kurz gesagt,kann in berechnet werden, verglichen mit der bei Verwendung der Definition erforderlichen -Zeit.n !O ( n ( logn )3LogLogn )Ω ( n2Logn )
Was das Lehrbuch vielleicht bedeutete, war die Divide-and-Conquer-Methode. Man kann die Multiplikationen reduzieren, indem man das reguläre Muster des Produkts verwendet.n - 1
LassenBezeichnen Sie als geeignete Schreibweise. Ordne die Faktoren von als
Nehmen wir nun an, dass für eine ganze Zahl . (Dies ist eine nützliche Annahme, um Komplikationen in der folgenden Diskussion zu vermeiden, und die Idee kann auf das allgemeine .) Dannund indem Sie diese Wiederholung erweitern,
Rechnen1 ⋅ 3 ⋅ 5 ⋯ ( 2 n - 1 ) ( 2 n ) ! = 1 ≤ 2 ≤ 3 ≤ ( 2 n ) ( 2 n ) ! = n ! ≤ 2 n ≤ 3 ≤ 5 ≤ 7 ≤ ( 2 n - 1 ) . n = 2 k k >n ?1 ⋅ 3 ⋅ 5 ⋯ ( 2 n - 1 )( 2 n ) ! = 1 ≤ 2 ≤ 3 ≤ ( 2 n )
( 2 n ) ! = n ! ⋅ 2n≤ 3 ≤ 5 ≤ 7 ≤ ( 2 n - 1 ) .
n = 2kn ( 2 k ) ! = ( 2 k - 1 ) ! 2 2 k - 1 ( 2 k - 1 ) ? ( 2 k ) ! = ( 2 2 k - 1 + 2 k - 2 + ⋯ + 2 0 ) k - 1 ∏ i = 0 ( 2 i )k > 0n( 2k) ! = ( 2k - 1) ! 22k - 1( 2k - 1) ?( 2 k & ndash ; 1 ) & dgr ; ( K - 2 ) + 2 k - 1 - 2 2 2 k - 2 2 2 k - 1( 2k) ! = ( 22k - 1+ 2k - 2+ ⋯ + 20) ∏i = 0k - 1( 2ich) ? = ( 22k- 1) ∏i = 1k - 1( 2ich) ? .
( 2k - 1) ?und das Multiplizieren der Teilprodukte in jeder Stufe ergibt Multiplikationen. Dies ist eine Verbesserung um einen Faktor von fast gegenüber Multiplikationen unter Verwendung der Definition. Einige zusätzliche Operationen sind erforderlich, um die Potenz von zu berechnen , aber in der Binärarithmetik kann dies kostengünstig durchgeführt werden (je nachdem, was genau erforderlich ist, muss möglicherweise nur ein Suffix von Nullen hinzugefügt werden ).
( k - 2 ) + 2k - 1- 222k- 222k- 1
Der folgende Ruby-Code implementiert eine vereinfachte Version davon. Dies vermeidet nicht die Neuberechnung vonsogar wo es das könnte: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)
Auch dieser First-Pass-Code verbessert das Triviale
f = 1; (1..32768).map{ |i| f *= i }; print f
um etwa 20% in meinen Tests.
Mit ein wenig Arbeit kann dies weiter verbessert werden, und es entfällt auch die Anforderung, dass eine Potenz von (siehe die ausführliche Diskussion ).2n2
Denken Sie daran, dass die Fakultätsfunktion so schnell wächst, dass Sie Ganzzahlen beliebiger Größe benötigen , um von effizienteren Techniken als dem naiven Ansatz zu profitieren. Die Fakultät von 21 ist bereits zu groß, um in ein 64-Bit-Format zu passen
unsigned long long int
.Soweit ich weiß, gibt es keinen Algorithmus, um zu berechnen(Fakultät von ), was schneller ist als die Multiplikationen.¹nn! n
Die Reihenfolge, in der Sie die Multiplikationen durchführen, ist jedoch von Bedeutung. Die Multiplikation auf einer Maschinen-Ganzzahl ist eine grundlegende Operation, die unabhängig vom Wert der Ganzzahl dieselbe Zeit benötigt. Bei Ganzzahlen beliebiger Größe hängt die Zeit, die zum Multiplizieren von a und b benötigt wird, von der Größe von a und b ab : Ein naiver Algorithmus arbeitet in der Zeit (wobei ist Anzahl der Stellen von - in beliebiger Basis, da das Ergebnis bis auf eine multiplikative Konstante dasselbe ist). Es gibt schnellere Multiplikationsalgorithmen , aber es gibt eine offensichtliche Untergrenze von| x | x Ω ( | a | + | b | ) max ( | a | , | b | )Θ(|a|⋅|b|) |x| x Ω(|a|+|b|) da die multiplikation mindestens alle ziffern lesen muss. Alle bekannten Multiplikationsalgorithmen wachsen in schneller als linear .max(|a|,|b|)
Vor diesem Hintergrund sollte der Wikipedia-Artikel Sinn machen.
Da die Komplexität von Multiplikationen von der Größe der zu multiplizierenden Ganzzahlen abhängt, können Sie Zeit sparen, indem Sie Multiplikationen in einer Reihenfolge anordnen, in der die zu multiplizierenden Zahlen klein gehalten werden. Es funktioniert besser, wenn Sie dafür sorgen, dass die Zahlen ungefähr gleich groß sind. Die “Teilung in zwei Hälften”, auf die sich Ihr Lehrbuch bezieht, besteht aus dem folgenden Divide-and-Conquer- Ansatz zur Multiplikation einer (Multi-) Menge von ganzen Zahlen:
Weitere Einzelheiten finden Sie im GMP-Handbuch .
Es gibt noch schnellere Methoden, die nicht nur die Faktoren bis neu ordnen , sondern die Zahlen auch aufteilen, indem sie in ihre Hauptfaktorisierung zerlegt und das resultierende sehr lange Produkt der meist kleinen ganzen Zahlen neu geordnet werden. Ich zitiere nur die Verweise aus dem Wikipedia-Artikel: „Über die Komplexität von Rechenfaktoren“ von Peter Borwein und Implementierungen von Peter Luschny .n1 n
¹ Es gibt schnellere Möglichkeiten, Näherungen für berechnenAber das berechnet nicht mehr die Fakultät, sondern eine Annäherung.n!
quelle
Da die Fakultätsfunktion so schnell wächst, kann Ihr Computer nur speichernfür relativ kleine . Zum Beispiel kann ein Double Werte bis zu speichern. Also, wenn Sie einen wirklich schnellen Algorithmus für die Berechnung vonVerwenden Sie einfach einen Tisch der Größe .n 171 ! n ! 171n! n 171! n! 171
Die Frage wird interessanter, wenn Sie an Oder an der Funktion (oder an ) interessiert sind . In all diesen Fällen (einschließlich ) Verstehe ich den Kommentar in Ihrem Lehrbuch nicht wirklich.Γ log Γ n !log(n!) Γ logΓ n!
Abgesehen davon sind Ihre iterativen und rekursiven Algorithmen äquivalent (bis zu Gleitkommafehlern), da Sie die Schwanzrekursion verwenden.
quelle