Ich weiß, dass der Algorithmus von Euclid der beste Algorithmus ist, um den GCD (Great Common Divisor) einer Liste positiver Ganzzahlen zu erhalten. In der Praxis können Sie diesen Algorithmus jedoch auf verschiedene Arten codieren. (In meinem Fall habe ich mich für Java entschieden, aber C / C ++ könnte eine andere Option sein).
Ich muss den effizientesten Code in meinem Programm verwenden.
Im rekursiven Modus können Sie schreiben:
static long gcd (long a, long b){
a = Math.abs(a); b = Math.abs(b);
return (b==0) ? a : gcd(b, a%b);
}
Und im iterativen Modus sieht es so aus:
static long gcd (long a, long b) {
long r, i;
while(b!=0){
r = a % b;
a = b;
b = r;
}
return a;
}
Es gibt auch den Binäralgorithmus für die GCD, der einfach so codiert werden kann:
int gcd (int a, int b)
{
while(b) b ^= a ^= b ^= a %= b;
return a;
}
algorithms
recursion
arithmetic
Jonaprieto
quelle
quelle
Antworten:
Ihre beiden Algorithmen sind äquivalent (zumindest für positive Ganzzahlen, was mit negativen Ganzzahlen in der imperativen Version passiert, hängt von Javas Semantik ab,einich bich ich
%
die ich nicht auswendig kann). In der rekursiven Version sei und b i das Argument des i- ten rekursiven Aufrufs: a i + 1 = b i b i + 1 = a i m o d b iIn der imperativen Version sei und b ' i der Wert der Variablen und am Anfang der i- ten Iteration der Schleife. a ' i + 1 = b ' i b ' i + 1 = a ' i m o d b ' iein′ich b′ich ich
a
b
Beachten Sie eine Ähnlichkeit? Ihre imperative Version und Ihre rekursive Version berechnen genau dieselben Werte. Außerdem enden beide zur gleichen Zeit, wenn (bzw. a ' i = 0 ), so dass sie die gleiche Anzahl von Iterationen ausführen. Algorithmisch gesehen gibt es also keinen Unterschied zwischen den beiden. Jeder Unterschied ist eine Frage der Implementierung, die in hohem Maße vom Compiler, der Hardware, auf der er ausgeführt wird, und möglicherweise vom Betriebssystem und den gleichzeitig ausgeführten Programmen abhängt.einich= 0 ein′ich= 0
Die rekursive Version führt nur rekursive Tail-Aufrufe aus . Die meisten Compiler für imperative Sprachen optimieren diese nicht. Daher ist es wahrscheinlich, dass der von ihnen generierte Code bei jeder Iteration ein wenig Zeit und Speicher verschwendet, um einen Stapelrahmen zu erstellen. Mit einem Compiler, der Tail Calls optimiert (Compiler für funktionale Sprachen fast immer), kann der generierte Maschinencode für beide gleich sein (vorausgesetzt, Sie harmonisieren diese Aufrufe
abs
).quelle
Für kleine Zahlen reicht der binäre GCD-Algorithmus aus.
GMP, eine gut gewartete und in der Praxis getestete Bibliothek, wird nach dem Überschreiten eines bestimmten Schwellenwerts, einer Verallgemeinerung von Lehmers Algorithmus, auf einen speziellen Halb-GCD-Algorithmus umstellen. Lehmers verwendet eine Matrixmultiplikation, um die Standard-Euklidian-Algorithmen zu verbessern. Gemäß den Dokumenten ist die asymptotische Laufzeit von sowohl HGCD als auch GCD
O(M(N)*log(N))
, wobeiM(N)
die Zeit zum Multiplizieren von zwei N-Glied-Zahlen ist.Alle Details zu ihrem Algorithmus finden Sie hier .
quelle
Wie ich weiß, unterstützt Java im Allgemeinen keine Schwanzrekursionsoptimierung, aber Sie können Ihre Java-Implementierung dafür testen. Wenn dies nicht unterstützt wird, sollte eine einfache
for
Schleife schneller sein, andernfalls sollte die Rekursion genauso schnell sein. Auf der anderen Seite handelt es sich um Bit-Optimierungen. Wählen Sie den Code, den Sie für einfacher und besser lesbar halten.Ich sollte auch beachten , dass der schnellste GCD Algorithmus ist nicht Euklids Algorithmus, Lehmer-Algorithmus ist ein bisschen schneller.
quelle
Verwenden Sie zunächst keine Rekursivität, um eine enge Schleife zu ersetzen. Es ist langsam. Verlassen Sie sich nicht auf den Compiler, um es zu optimieren. Zweitens rufen Sie in Ihrem Code in jedem rekursiven Aufruf Math.abs () auf, was unbrauchbar ist.
In Ihrer Schleife können Sie auf einfache Weise temporäre Variablen vermeiden und die ganze Zeit a und b tauschen.
Durch das Vertauschen mit a ^ = b ^ = a ^ = b wird die Quelle kürzer, die Ausführung erfordert jedoch viele Anweisungen. Es ist langsamer als der langweilige Tausch mit einer temporären Variablen.
quelle
Für kleine Zahlen ist% eine ziemlich teure Operation, vielleicht die einfachere rekursive
ist schneller? (Sorry, Mathematica-Code und nicht C ++)
quelle
Der Euklid-Algorithmus ist am effizientesten für die Berechnung von GCD:
Beispiel:-
quelle