Finden des kleinsten gemeinsamen Vielfachen in Java

1. Übersicht

Das kleinste gemeinsame Vielfache (Least Common Multiple, LCM) von zwei Ganzzahlen ungleich Null (a, b) ist die kleinste positive Ganzzahl, die sowohl durch a als auch durch b perfekt teilbar ist .

In diesem Tutorial lernen wir verschiedene Ansätze kennen, um das LCM von zwei oder mehr Zahlen zu ermitteln. Wir müssen beachten, dass negative ganze Zahlen und Null keine Kandidaten für LCM sind .

2. Berechnung des LCM zweier Zahlen mit einem einfachen Algorithmus

Wir können das LCM von zwei Zahlen finden, indem wir die einfache Tatsache verwenden, dass die Multiplikation eine wiederholte Addition ist .

2.1. Algorithmus

Der einfache Algorithmus zum Auffinden des LCM ist ein iterativer Ansatz, bei dem einige grundlegende Eigenschaften des LCM mit zwei Zahlen verwendet werden.

Erstens wissen wir, dass das LCM einer beliebigen Zahl mit Null selbst Null ist . Wir können also die Prozedur vorzeitig verlassen, wenn eine der angegebenen Ganzzahlen 0 ist.

Zweitens können wir auch die Tatsache nutzen, dass die Untergrenze des LCM von zwei Ganzzahlen ungleich Null der größere der absoluten Werte der beiden Zahlen ist .

Darüber hinaus kann das LCM, wie bereits erläutert, niemals eine negative ganze Zahl sein. Wir werden also nur Absolutwerte der ganzen Zahlen verwenden, um die möglichen Vielfachen zu finden, bis wir ein gemeinsames Vielfaches finden.

Sehen wir uns das genaue Verfahren an, das wir zur Bestimmung von lcm (a, b) befolgen müssen:

  1. Wenn a = 0 oder b = 0, kehren Sie mit lcm (a, b) = 0 zurück, andernfalls fahren Sie mit Schritt 2 fort.
  2. Berechnen Sie die absoluten Werte der beiden Zahlen.
  3. Initialisieren Sie lcm als den höheren der beiden in Schritt 2 berechneten Werte.
  4. Wenn lcm durch den unteren Absolutwert teilbar ist, kehren Sie zurück.
  5. Erhöhen Sie lcm um den höheren Absolutwert zwischen den beiden und fahren Sie mit Schritt 4 fort.

Bevor wir mit der Implementierung dieses einfachen Ansatzes beginnen, führen wir einen Probelauf durch, um lcm (12, 18) zu finden.

Da sowohl 12 als auch 18 positiv sind, springen wir zu Schritt 3, initialisieren lcm = max (12, 18) = 18 und fahren fort.

In unserer ersten Iteration ist lcm = 18, was nicht perfekt durch 12 teilbar ist. Also erhöhen wir es um 18 und fahren fort.

In der zweiten Iteration können wir sehen, dass lcm = 36 ist und jetzt perfekt durch 12 teilbar ist. Wir können also vom Algorithmus zurückkehren und daraus schließen, dass lcm (12, 18) 36 ist.

2.2. Implementierung

Lassen Sie uns den Algorithmus in Java implementieren. Unsere lcm () -Methode muss zwei ganzzahlige Argumente akzeptieren und deren LCM als Rückgabewert angeben .

Wir können feststellen, dass der obige Algorithmus einige mathematische Operationen an den Zahlen ausführt, z. B. das Finden von Absolut-, Minimal- und Maximalwerten. Zu diesem Zweck können wir die entsprechenden statischen Methoden der Math- Klasse wie abs () , min () bzw. max () verwenden.

Implementieren wir unsere lcm () -Methode:

public static int lcm(int number1, int number2) { if (number1 == 0 || number2 == 0) { return 0; } int absNumber1 = Math.abs(number1); int absNumber2 = Math.abs(number2); int absHigherNumber = Math.max(absNumber1, absNumber2); int absLowerNumber = Math.min(absNumber1, absNumber2); int lcm = absHigherNumber; while (lcm % absLowerNumber != 0) { lcm += absHigherNumber; } return lcm; }

Als nächstes validieren wir auch diese Methode:

@Test public void testLCM() { Assert.assertEquals(36, lcm(12, 18)); }

Der obige Testfall überprüft die Richtigkeit der lcm () -Methode, indem behauptet wird, dass lcm (12, 18) 36 ist.

3. Verwenden des Prime Factorization-Ansatzes

Der Grundsatz der Arithmetik besagt, dass es möglich ist, jede ganze Zahl größer als eins als Produkt von Potenzen von Primzahlen eindeutig auszudrücken.

Für jede ganze Zahl N> 1 gilt also N = (2k1) * (3k2) * (5k3) *…

Unter Verwendung des Ergebnisses dieses Theorems werden wir nun den Primfaktorisierungsansatz verstehen, um das LCM von zwei Zahlen zu finden.

3.1. Algorithmus

Der Primfaktorisierungsansatz berechnet das LCM aus der Primzerlegung der beiden Zahlen. Wir können die Primfaktoren und Exponenten aus der Primfaktorisierung verwenden, um den LCM der beiden Zahlen zu berechnen:

Wann, | a | = (2p1) * (3p2) * (5p3) *…

und | b | = (2q1) * (3q2) * (5q3) *…

dann ist lcm (a, b) = (2 max (p 1 , q 1 )) * (3 max (p 2 , q 2 )) * (5 max (p 3 , q 3 ))…

Mal sehen, wie man den LCM von 12 und 18 mit diesem Ansatz berechnet:

Erstens müssen wir die absoluten Werte der beiden Zahlen als Produkte von Primfaktoren darstellen:

12 = 2 · 2 · 3 = 2² · 3¹

18 = 2 · 3 · 3 = 2¹ · 3²

Wir können hier feststellen, dass die Hauptfaktoren in den obigen Darstellungen 2 und 3 sind.

Als nächstes bestimmen wir den Exponenten jedes Primfaktors für das LCM. Wir tun dies, indem wir seine höhere Kraft aus den beiden Darstellungen entnehmen.

Bei Verwendung dieser Strategie beträgt die Potenz von 2 im LCM max (2, 1) = 2 und die Potenz von 3 im LCM max (1, 2) = 2.

Schließlich können wir das LCM berechnen, indem wir die Primfaktoren mit einer entsprechenden Leistung multiplizieren, die im vorherigen Schritt erhalten wurde. Folglich haben wir lcm (12, 18) = 2² * 3² = 36.

3.2. Implementierung

Unsere Java-Implementierung verwendet eine Primfaktorisierungsdarstellung der beiden Zahlen, um das LCM zu finden.

Zu diesem Zweck muss unsere Methode getPrimeFactors () ein ganzzahliges Argument akzeptieren und uns die Darstellung der Primfaktorisierung geben. In Java können wir die Primfaktorisierung einer Zahl mithilfe einer HashMap darstellen, wobei jeder Schlüssel den Primfaktor und der dem Schlüssel zugeordnete Wert den Exponenten des entsprechenden Faktors bezeichnet.

Sehen wir uns eine iterative Implementierung der Methode getPrimeFactors () an :

public static Map getPrimeFactors(int number) { int absNumber = Math.abs(number); Map primeFactorsMap = new HashMap(); for (int factor = 2; factor <= absNumber; factor++) { while (absNumber % factor == 0) { Integer power = primeFactorsMap.get(factor); if (power == null) { power = 0; } primeFactorsMap.put(factor, power + 1); absNumber /= factor; } } return primeFactorsMap; }

We know that the prime factorization maps of 12 and 18 are {2 → 2, 3 → 1} and {2 → 1, 3 → 2} respectively. Let's use this to test the above method:

@Test public void testGetPrimeFactors() { Map expectedPrimeFactorsMapForTwelve = new HashMap(); expectedPrimeFactorsMapForTwelve.put(2, 2); expectedPrimeFactorsMapForTwelve.put(3, 1); Assert.assertEquals(expectedPrimeFactorsMapForTwelve, PrimeFactorizationAlgorithm.getPrimeFactors(12)); Map expectedPrimeFactorsMapForEighteen = new HashMap(); expectedPrimeFactorsMapForEighteen.put(2, 1); expectedPrimeFactorsMapForEighteen.put(3, 2); Assert.assertEquals(expectedPrimeFactorsMapForEighteen, PrimeFactorizationAlgorithm.getPrimeFactors(18)); }

Our lcm() method first uses the getPrimeFactors() method to find prime factorization map for each number. Next, it uses the prime factorization map of both the numbers to find their LCM. Let's see an iterative implementation of this method:

public static int lcm(int number1, int number2) { if(number1 == 0 || number2 == 0) { return 0; } Map primeFactorsForNum1 = getPrimeFactors(number1); Map primeFactorsForNum2 = getPrimeFactors(number2); Set primeFactorsUnionSet = new HashSet(primeFactorsForNum1.keySet()); primeFactorsUnionSet.addAll(primeFactorsForNum2.keySet()); int lcm = 1; for (Integer primeFactor : primeFactorsUnionSet) { lcm *= Math.pow(primeFactor, Math.max(primeFactorsForNum1.getOrDefault(primeFactor, 0), primeFactorsForNum2.getOrDefault(primeFactor, 0))); } return lcm; }

As a good practice, we shall now verify the logical correctness of the lcm() method:

@Test public void testLCM() { Assert.assertEquals(36, PrimeFactorizationAlgorithm.lcm(12, 18)); }

4. Using the Euclidean Algorithm

There's an interesting relation between the LCM and GCD (Greatest Common Divisor) of two numbers that says that the absolute value of the product of two numbers is equal to the product of their GCD and LCM.

As stated, gcd(a, b) * lcm(a, b) = |a * b|.

Consequently, lcm(a, b) = |a * b|/gcd(a, b).

Using this formula, our original problem of finding lcm(a,b) has now been reduced to just finding gcd(a,b).

Granted, there are multiple strategies to finding GCD of two numbers. However, the Euclidean algorithm is known to be one of the most efficient of all.

For this reason, let's briefly understand the crux of this algorithm, which can be summed up in two relations:

  • gcd (a, b) = gcd(|a%b|, |a| ); where |a| >= |b|
  • gcd(p, 0) = gcd(0, p) = |p|

Let's see how we can find lcm(12, 18) using the above relations:

We have gcd(12, 18) = gcd(18%12, 12) = gcd(6,12) = gcd(12%6, 6) = gcd(0, 6) = 6

Therefore, lcm(12, 18) = |12 x 18| / gcd(12, 18) = (12 x 18) / 6 = 36

We'll now see a recursive implementation of the Euclidean algorithm:

public static int gcd(int number1, int number2) { if (number1 == 0 || number2 == 0) { return number1 + number2; } else { int absNumber1 = Math.abs(number1); int absNumber2 = Math.abs(number2); int biggerValue = Math.max(absNumber1, absNumber2); int smallerValue = Math.min(absNumber1, absNumber2); return gcd(biggerValue % smallerValue, smallerValue); } }

The above implementation uses the absolute values of numbers — since GCD is the largest positive integer that perfectly divides the two numbers, we're not interested in negative divisors.

We're now ready to verify if the above implementation works as expected:

@Test public void testGCD() { Assert.assertEquals(6, EuclideanAlgorithm.gcd(12, 18)); }

4.1. LCM of Two Numbers

Using the earlier method to find GCD, we can now easily calculate LCM. Again, our lcm() method needs to accept two integers as input to return their LCM. Let's see how we can implement this method in Java:

public static int lcm(int number1, int number2) { if (number1 == 0 || number2 == 0) return 0; else { int gcd = gcd(number1, number2); return Math.abs(number1 * number2) / gcd; } }

We can now verify the functionality of the above method:

@Test public void testLCM() { Assert.assertEquals(36, EuclideanAlgorithm.lcm(12, 18)); }

4.2. LCM of Large Numbers Using the BigInteger Class

To calculate the LCM of large numbers, we can leverage the BigInteger class.

Internally, the gcd() method of the BigInteger class uses a hybrid algorithm to optimize computation performance. Moreover, since the BigInteger objects are immutable, the implementation leverages mutable instances of the MutableBigInteger class to avoid frequent memory reallocations.

To begin with, it uses the conventional Euclidean algorithm to repeatedly replace the higher integer by its modulus with the lower integer.

As a result, the pair not only gets smaller and smaller but also closer to each other after successive divisions. Eventually, the difference in the number of ints required to hold the magnitude of the two MutableBigInteger objects in their respective int[] value arrays reaches either 1 or 0.

At this stage, the strategy is switched to the Binary GCD algorithm to get even faster computation results.

In this case, as well, we'll compute LCM by dividing the absolute value of the product of the numbers by their GCD. Similar to our prior examples, our lcm() method takes two BigInteger values as input and returns the LCM for the two numbers as a BigInteger. Let's see it in action:

public static BigInteger lcm(BigInteger number1, BigInteger number2) { BigInteger gcd = number1.gcd(number2); BigInteger absProduct = number1.multiply(number2).abs(); return absProduct.divide(gcd); }

Finally, we can verify this with a test case:

@Test public void testLCM() { BigInteger number1 = new BigInteger("12"); BigInteger number2 = new BigInteger("18"); BigInteger expectedLCM = new BigInteger("36"); Assert.assertEquals(expectedLCM, BigIntegerLCM.lcm(number1, number2)); }

5. Conclusion

In this tutorial, we discussed various methods to find the least common multiple of two numbers in Java.

Moreover, we also learned about the relation between the product of numbers with their LCM and GCD. Given algorithms that can compute the GCD of two numbers efficiently, we've also reduced the problem of LCM calculation to one of GCD computation.

Wie immer ist der vollständige Quellcode für die in diesem Artikel verwendete Java-Implementierung auf GitHub verfügbar.