Алгоритм Евклида - Алгоритмика
Алгоритм Евклида

Алгоритм Евклида

Наибольшим общим делителем (англ. greatest common divisor) целых неотрицательных чисел aa и bb называется наибольшее число xx, которое делит одновременно и aa, и bb. gcd(a,b)=maxk:  kakbk \gcd(a, b) = \max_{k: \; k|a \, \land \, k | b} k

Когда оба числа равны нулю, результат не определён — подойдёт сколько угодно большое число. За исключением этого случая, верно следующее наблюдение: если одно из чисел равно нулю, то их gcd\gcd равен второму числу.

#Алгоритм нахождения

Алгоритм Евклида находит gcd\gcd двух чисел aa и bb за O(logmin(a,b))O(\log \min(a, b)), основываясь на следующей несложной формуле:

gcd(a,b)={a,b=0gcd(b,ab),b>0 \gcd(a, b) = \begin{cases} a, & b = 0 \\ \gcd(b,\, a - b), & b > 0 \end{cases}

Здесь предполагается, что a>ba > b.

Докажем корректность этой формулы:

  • Если g=gcd(a,b)g = \gcd(a, b) делит и aa, и bb, то их разность (ab)(a-b) тоже будет делиться на gg.

  • Никакой больший делитель dd числа bb не может делить число (ab)(a-b): если d>gd > g, то dd не может делить aa, а значит и не делит (ab)(a - b).

Прямая рекурсивная реализация:

int gcd(int a, int b) {
    if (a < b)
        swap(a, b);
    if (b == 0)
        return a;
    else
        return gcd(b, a - b);
}

Этот алгоритм может работать долго — например, на паре (109,1)(10^9, 1) он сделает миллиард итераций.

Идея дальнейшей оптимизации в том, чтобы вычитать из aa не одно bb за раз, а столько, чтобы в следующий раз aa и bb уже поменялись местами — чтобы новое bb стало меньше нового aa. Простой способ этого достичь — просто вычесть bb из aa сразу максимально возможное число раз, то есть взять вместо нового bb остаток от деления aa на bb:

gcd(a,b)={a,b=0gcd(b,amodb),b>0 \gcd(a, b) = \begin{cases} a, & b = 0 \\ \gcd(b,\, a \bmod b), & b > 0 \end{cases}

Реализация:

int gcd(int a, int b) {
    if (b == 0)
        return a;
    else
        return gcd(b, a % b);
}

Чуть более быстрая итеративная форма:

int gcd(int a, int b) {
    while (b > 0) {
        a %= b;
        swap(a, b);
    }
    return a;
}

В современном C++ есть встроенная библиотечная функция gcd, которую рекомендуется использовать, не забывая про случай отрицательных чисел и (0,0)(0, 0).

Также помимо алгоритма Евклида существует в 2-3 раза более быстрый бинарный GCD.

#Время работы

Можно показать, что каждые две итерации меньшее число уменьшится хотя бы в два раза, а следовательно алгоритм работает за O(logmin(a,b))O(\log \min (a, b)). Эта оценка относится не только к худшему случаю, но и к среднему.

Время работы алгоритма на разных входных данных

Примечательно, что худшие входные данные для алгоритма — это соседние числа Фибоначчи. На графике они видны как синие точки в пропорциях золотого сечения.

Также иногда полезно знать, что нахождение gcd\gcd группы из nn чисел от 11 до AA будет работать не за O(nlogA)O(n \log A), а за O(n+logA)O(n + \log A) — это несложно доказать по индукции.