Я знаю, что алгоритм Евклида - лучший алгоритм для получения GCD (большой общий делитель) списка натуральных чисел. Но на практике вы можете кодировать этот алгоритм различными способами. (В моем случае я решил использовать Java, но C / C ++ может быть другим вариантом).
Мне нужно использовать максимально эффективный код в моей программе.
В рекурсивном режиме вы можете написать:
static long gcd (long a, long b){
a = Math.abs(a); b = Math.abs(b);
return (b==0) ? a : gcd(b, a%b);
}
А в итеративном режиме это выглядит так:
static long gcd (long a, long b) {
long r, i;
while(b!=0){
r = a % b;
a = b;
b = r;
}
return a;
}
Существует также двоичный алгоритм для GCD, который может быть закодирован просто так:
int gcd (int a, int b)
{
while(b) b ^= a ^= b ^= a %= b;
return a;
}
algorithms
recursion
arithmetic
jonaprieto
источник
источник
Ответы:
Ваши два алгоритма эквивалентны (по крайней мере для положительных целых чисел то, что происходит с отрицательными целыми числами в императивной версии, зависит от семантики Java, дляaя бя я
%
которой я не знаю наизусть). В рекурсивной версии пусть и будут аргументом го рекурсивного вызова:В императивной версии пусть и будут значениями переменных и в начале й итерации цикла.a'я б'я я
a
b
Заметили сходство? Ваша императивная версия и рекурсивная версия рассчитывают абсолютно одинаковые значения. Кроме того, они оба заканчиваются в одно и то же время, когда (соответственно ), поэтому они выполняют одинаковое количество итераций. Таким образом, с точки зрения алгоритма, нет никакой разницы между ними. Любое различие будет зависеть от реализации, в значительной степени зависящей от компилятора, оборудования, на котором он работает, и, вполне возможно, от операционной системы и от того, какие другие программы работают одновременно.aя= 0 a'я= 0
Рекурсивная версия делает только хвостовые рекурсивные вызовы . Большинство компиляторов для императивных языков не оптимизируют их, поэтому вполне вероятно, что генерируемый ими код будет тратить немного времени и памяти на создание стекового фрейма на каждой итерации. С компилятором, который оптимизирует хвостовые вызовы (компиляторы для функциональных языков почти всегда это делают), сгенерированный машинный код вполне может быть одинаковым для обоих (при условии, что вы гармонизируете эти вызовы
abs
).источник
Для небольших чисел достаточно двоичного алгоритма GCD.
GMP, хорошо поддерживаемая и проверенная в реальных условиях библиотека, переключится на специальный алгоритм половины GCD после прохождения специального порога, обобщающего алгоритм Лемера. Лемер использует матричное умножение для улучшения стандартных евклидовых алгоритмов. Согласно документам, асимптотическое время работы как HGCD, так и GCD равно
O(M(N)*log(N))
, гдеM(N)
время умножения двух чисел N-конечностей.Полную информацию об их алгоритме можно найти здесь .
источник
Как я знаю, Java вообще не поддерживает оптимизацию хвостовой рекурсии, но вы можете протестировать свою реализацию Java на это; если он не поддерживает, простой
for
-loop должен быть быстрее, иначе рекурсия должна быть такой же быстрой. С другой стороны, это битовая оптимизация, выберите код, который вы считаете более простым и более читабельным.Следует также отметить, что самый быстрый алгоритм GCD - это не алгоритм Евклида, а алгоритм Лемера немного быстрее.
источник
Во-первых, не используйте рекурсивность для замены узкой петли. Это медленно. Не полагайтесь на компилятор, чтобы оптимизировать его. Во-вторых, в вашем коде вы вызываете Math.abs () для каждого рекурсивного вызова, что бесполезно.
В вашем цикле вы можете легко избегать временных переменных и постоянно менять местами a и b.
Обмен с использованием a ^ = b ^ = a ^ = b делает исходный код короче, но для его выполнения требуется много инструкций. Это будет медленнее, чем скучный своп с временной переменной.
источник
Для небольших чисел % довольно дорогая операция, возможно, более простая рекурсивная
быстрее? (Извините, код Mathematica, а не C ++)
источник
Алгоритм Евклида наиболее эффективен для расчета ГКД:
пример:-
источник