1. Обзор
То Наименьшее Общее Число (LCM) из двух ненулевых целых чисел (a, b) является наименьшим положительным целым числом, которое идеально делится на оба a и b .
В этом уроке мы узнаем о различных подходах к поиску LCM двух или более чисел. Мы должны отметить, что отрицательные целые числа и ноль не являются кандидатами на LCM .
2. Вычисление LCM из двух чисел С помощью простого алгоритма
Мы можем найти LCM двух чисел, используя простой факт, что умножение является повторным сложением .
2.1. Алгоритм
Простой алгоритм поиска LCM-это итерационный подход, который использует несколько фундаментальных свойств LCM двух чисел.
Во-первых, мы знаем, что LCM любого числа с нулем сам по себе равен нулю . Таким образом, мы можем сделать ранний выход из процедуры всякий раз, когда любое из заданных целых чисел равно 0.
Во-вторых, мы также можем использовать тот факт, что нижняя граница LCM двух ненулевых целых чисел является большей из абсолютных значений двух чисел .
Более того, как объяснялось ранее, LCM никогда не может быть отрицательным целым числом. Таким образом, мы будем использовать только абсолютные значения целых чисел для нахождения возможных кратных, пока не найдем общее кратное.
Давайте рассмотрим точную процедуру, которой мы должны следовать для определения lcm(a, b):
- Если или, то вернитесь с lcm(a,, иначе перейдите к шагу 2.
- Вычислите абсолютные значения двух чисел.
- Инициализируйте lcm как большее из двух значений, вычисленных на шаге 2.
- Если lcm делится на меньшее абсолютное значение, то возвращается.
- Увеличьте lcm на более высокое абсолютное значение среди двух и перейдите к шагу 4.
Прежде чем мы начнем с реализации этого простого подхода, давайте проведем сухой прогон, чтобы найти lcm(12, 18).
Поскольку и 12, и 18 являются положительными, давайте перейдем к шагу 3, инициализации(12,, и продолжим дальше.
В нашей первой итерации, которая не полностью делится на 12, мы увеличиваем ее на 18 и продолжаем.
На второй итерации мы видим, что и теперь идеально делится на 12. Таким образом, мы можем вернуться из алгоритма и сделать вывод, что lcm(12, 18) равен 36.
2.2. Реализация
Давайте реализуем алгоритм на Java. Наш метод lcm() должен принимать два целочисленных аргумента и давать их LCM в качестве возвращаемого значения.
Мы можем заметить, что приведенный выше алгоритм включает в себя выполнение нескольких математических операций над числами, таких как нахождение абсолютных, минимальных и максимальных значений. Для этой цели мы можем использовать соответствующие статические методы класса Math , такие как abs () , min (), и max () соответственно.
Давайте реализуем наш метод lcm() :
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; }
Далее, давайте также проверим этот метод:
@Test public void testLCM() { Assert.assertEquals(36, lcm(12, 18)); }
Приведенный выше тестовый пример проверяет правильность метода lcm () , утверждая, что lcm(12, 18) равен 36.
3. Использование подхода Простой факторизации
То фундаментальная теорема арифметики утверждает, что можно однозначно выразить каждое целое число, большее единицы, как произведение степеней простых чисел.
Итак, для любого целого числа N > 1 мы имеем N = (2 k1 ) * (3 k2 ) * (5 k3 ) *…
Используя результат этой теоремы, мы теперь поймем подход простой факторизации, чтобы найти LCM двух чисел.
3.1. Алгоритм
Подход простой факторизации вычисляет LCM из простого разложения двух чисел . Мы можем использовать простые множители и экспоненты из простой факторизации для вычисления LCM двух чисел:
Когда |a| = (2 p1 ) * (3 p2 ) * (5 p3 ) * …
и |b| = (2 q1
) * (3 q2
) * (5 q3
) * …
тогда/| lcm(a, b) = (2 max(p 1 , q 1 ) ) * (3 макс(p 2 , q 2 ) ) * (5 макс(p 3 , q 3 ) ) …
Давайте посмотрим, как рассчитать LCM 12 и 18, используя этот подход:
Во-первых, нам нужно представить абсолютные значения двух чисел как произведения простых множителей: * 2 * * 31 * 3 * * 32
Здесь мы можем заметить, что простые множители в приведенных выше представлениях равны 2 и 3.
Далее, давайте определим показатель каждого простого множителя для LCM. Мы делаем это, беря его высшую силу из двух представлений.
Используя эту стратегию, мощность 2 в LCM будет максимальной(2,, и мощность 3 в LCM будет максимальной(1,.
Наконец, мы можем вычислить LCM, умножив простые множители с соответствующей степенью, полученной на предыдущем шаге. Следовательно, мы имеем lcm(12, *.
3.2. Реализация
Наша реализация Java использует простое факторизационное представление двух чисел, чтобы найти LCM.
Для этого наш метод getPrimeFactors() должен принять целочисленный аргумент и предоставить нам его представление простой факторизации. В Java мы можем представить простую факторизацию числа с помощью HashMap , где каждый ключ обозначает простой фактор, а значение, связанное с ключом, обозначает показатель соответствующего фактора.
Давайте рассмотрим итеративную реализацию метода getPrimeFactors() :
public static MapgetPrimeFactors(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; }
Мы знаем, что простые карты факторизации 12 и 18 являются {2 → 2, 3 → 1} и {2 → 1, 3 → 2} соответственно. Давайте воспользуемся этим для проверки приведенного выше метода:
@Test public void testGetPrimeFactors() { MapexpectedPrimeFactorsMapForTwelve = 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)); }
Наш метод lcm() сначала использует метод getPrimeFactors() для поиска карты простой факторизации для каждого числа. Затем он использует карту простой факторизации обоих чисел, чтобы найти их LCM. Давайте рассмотрим интерактивную реализацию этого метода:
public static int lcm(int number1, int number2) { if(number1 == 0 || number2 == 0) { return 0; } MapprimeFactorsForNum1 = 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; }
В качестве хорошей практики мы теперь проверим логическую корректность метода lcm() :
@Test public void testLCM() { Assert.assertEquals(36, PrimeFactorizationAlgorithm.lcm(12, 18)); }
4. Использование Евклидова алгоритма
Существует интересная связь между LCM и GCD (Наибольший общий делитель) двух чисел, которая говорит, что абсолютное значение произведения двух чисел равно произведению их GCD и LCM .
Как уже говорилось, gcd(a, b) * lcm(a, b) = |a * b|.
Следовательно, lcm(a, b) = |a * b|/gcd(a, b) .
Используя эту формулу, наша первоначальная задача поиска lcm(a,b) теперь была сведена к простому нахождению gcd(a,b).
Конечно, существует несколько стратегий поиска GCD из двух чисел. Однако известно, что евклидов алгоритм является одним из самых эффективных из всех.
По этой причине давайте вкратце разберемся в сути этого алгоритма, который можно суммировать в двух соотношениях:
- gcd (a,(|a%b|, |a| ); где |a| >= |b|
- gcd(p,(0, p) = |p|
Давайте посмотрим, как мы можем найти lcm(12, 18), используя приведенные выше соотношения:
У нас есть gcd(12,(18%12,(6,12)(12%6,(0,
Следовательно, lcm(12, 18) = |12 x 18|/gcd(12, 18) = (12 x 18)/
Теперь мы увидим рекурсивную реализацию алгоритма Евклида :
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); } }
В приведенной выше реализации используются абсолютные значения чисел — поскольку GCD является самым большим положительным целым числом, которое идеально делит два числа, нас не интересуют отрицательные делители.
Теперь мы готовы проверить, работает ли вышеуказанная реализация так, как ожидалось:
@Test public void testGCD() { Assert.assertEquals(6, EuclideanAlgorithm.gcd(12, 18)); }
4.1. LCM из двух чисел
Используя более ранний метод для поиска GCD, теперь мы можем легко вычислить LCM. Опять же, наш метод lcm() должен принимать два целых числа в качестве входных данных, чтобы вернуть их LCM. Давайте посмотрим, как мы можем реализовать этот метод в 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; } }
Теперь мы можем проверить функциональность вышеуказанного метода:
@Test public void testLCM() { Assert.assertEquals(36, EuclideanAlgorithm.lcm(12, 18)); }
4.2. LCM больших чисел с использованием класса BigInteger
Чтобы вычислить LCM больших чисел, мы можем использовать класс BigInteger |/.
Внутренне метод gcd() класса BigInteger использует гибридный алгоритм для оптимизации производительности вычислений. Кроме того, поскольку объекты BigInteger являются неизменяемыми , реализация использует изменяемые экземпляры класса MutableBigInteger , чтобы избежать частых перераспределений памяти .
Начнем с того, что он использует обычный евклидов алгоритм для многократной замены более высокого целого числа по модулю на более низкое целое число.
В результате пара не только становится все меньше и меньше, но и ближе друг к другу после последовательных делений . В конечном итоге разница в количестве int s, необходимых для хранения величины двух MutableBigInteger объектов в соответствующих массивах значений int [] , достигает либо 1, либо 0.
На этом этапе стратегия переключается на Бинарный алгоритм GCD , чтобы получить еще более быстрые результаты вычислений .
В этом случае мы также вычислим LCM, разделив абсолютное значение произведения чисел на их GCD. Как и в предыдущих примерах, наш метод lcm() принимает два значения BigInteger в качестве входных данных и возвращает LCM для двух чисел в виде BigInteger . Давайте посмотрим на это в действии:
public static BigInteger lcm(BigInteger number1, BigInteger number2) { BigInteger gcd = number1.gcd(number2); BigInteger absProduct = number1.multiply(number2).abs(); return absProduct.divide(gcd); }
Наконец, мы можем проверить это с помощью тестового случая:
@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. Заключение
В этом уроке мы обсудили различные методы поиска наименьшего общего кратного двум числам в Java.
Кроме того, мы также узнали о связи между произведением чисел с их LCM и GCD. Учитывая алгоритмы, которые могут эффективно вычислять GCD двух чисел, мы также сократили задачу вычисления LCM до одного из вычислений GCD.
Как всегда, полный исходный код реализации Java, используемой в этой статье, доступен на GitHub .