Как работает Евклидово Алгоритм?

Я просто нашел этот алгоритм для вычисления наибольшего общего делителя в своих лекциях:

public static int gcd( int a, int b ) {
    while (b != 0) {
        final int r = a % b;
        a = b;
        b = r;
    }
    return a;
}

Итак, r - остаток при делении b на a (получить mod). Затем b присваивается a, а остаток присваивается b, а a возвращается. Я не могу за свою жизнь видеть, как это работает!

И тогда, по-видимому, этот алгоритм не работает для всех случаев, и тогда он должен использоваться:

public static int gcd( int a, int b ) {
    final int gcd;
    if (b != 0) {
        final int q = a / b;
        final int r = a % b; // a == r + q * b AND r == a - q * b.
        gcd = gcd( b, r );
    } else {
        gcd = a;
    }
    return gcd;
}

Я не понимаю причины этого. Обычно я получаю рекурсию и хорошо разбираюсь в Java, но это ускользает от меня. Помогите пожалуйста?

Ответ 1

В статье Wikipedia содержится объяснение, но это нелегко найти сразу (также, процедура + доказательство не всегда ответьте на вопрос "почему это работает" ).

В основном это сводится к тому, что для двух целых чисел a, b (при условии a >= b) всегда можно написать a = bq + r, где r < б.

Если d = gcd (a, b), то мы можем написать a = ds и b = dt. Итак, мы имеем ds = qdt + r. Поскольку левая часть делится на d, правая часть также должна делиться на d. А так как qdt делится на d, вывод состоит в том, что r также должен быть делимым на d.

Подводя итог: имеем a = bq + r, где r < b и a, b и r делятся на gcd (a, b).

Так как a >= b > r, мы имеем два случая:

  • Если r = 0, то a = bq, и, следовательно, b делит как b, так и a. Следовательно, gcd (a, b) = b.
  • В противном случае (r > 0) мы можем свести задачу нахождения gcd (a, b) к задаче нахождения gcd (b, r), которая является точно таким же числом (поскольку a, b и r все делятся по d).

Почему это сокращение? Поскольку r < б. Таким образом, мы имеем дело с числами, которые определенно меньше. Это означает, что нам нужно лишь применить это сокращение к конечному числу раз, прежде чем мы достигнем г = 0.

Теперь, r = a% b, который, надеюсь, объяснит ваш код.

Ответ 2

Они эквивалентны. Прежде всего следует отметить, что q во второй программе вообще не используется. Другая разница - это просто итерация против рекурсии.

Что касается того, почему это работает, ссылка на Википедии, связанная выше, хороша. Первая иллюстрация, в частности, эффективна для интуитивной передачи "почему", а анимация ниже иллюстрирует "как".

Ответ 3

учитывая, что "q" никогда не используется, я не вижу разницы между вашей простой итеративной функцией и рекурсивной итеративной функцией... оба делают

gdc(first number, second number)
  as long as (second number > 0) {
      int remainder = first % second;
      gcd = try(second as first, remainder as second);
  }
}

Запрет попытки применить это к нецелым, при каких обстоятельствах этот алгоритм терпит неудачу?

(также см. http://en.wikipedia.org/wiki/Euclidean_algorithm для подробной информации)

Ответ 4

Вот интересное сообщение в блоге: Tominology.

Там, где обсуждается много интуиции, стоящей за евклидовым алгоритмом, она реализована в JavaScript, но я считаю, что, если кто-то хочет, не сложно преобразовать код в Java.

Ответ 5

Здесь - очень полезное объяснение, которое я нашел.

Для тех, кто слишком ленив, чтобы открыть его, вот что он говорит:

Рассмотрим пример, когда вам нужно было найти GCD (3084,1424). Предположим, что d является GCD. Это означает, что d | 3084 и d | 1424 (используя символ "|", чтобы сказать "делит" ).

Отсюда следует, что d | (3084-1424). Теперь мы попытаемся уменьшить эти числа, которые делятся на d (в данном случае 3084 и 1024) как можно больше, так что мы достигнем 0 как одно из чисел. Помните, что GCD (a, 0) является a.

Так как d | (3084 - 1424), следует, что d | (3084-2 (1424)) что означает d | 236.
Подсказка: (3084 - 2 * 1424 = 236)

Теперь забудьте о начальных числах, нам просто нужно решить для d, и мы знаем, что d является наибольшим числом, которое делит 236, 1424 и 3084. Таким образом, мы используем меньшие два числа, чтобы продолжить, потому что оно будет сходиться проблема в направлении 0.

d | 1424 и d | 236 следует, что d | (1424-236).
Итак, d | (1424 - 6 (236)) = > d | 8.

Теперь мы знаем, что d - наибольшее число, делящее 8, 236, 1424 и 3084. Взяв меньшие два, мы имеем

d | 236 и d | 8, откуда следует d | (236-8).
Итак, d | (236 - 29 (8)) = > d | 4.

Снова список чисел, делящихся на d, увеличивается и сходится (числа становятся меньше, ближе к 0). В настоящее время d является наибольшим числом, которое делит 4, 8, 236, 1424, 3084.

Выполняя те же действия,

d | 8 и d | 4 следует, что d | (8-4).
Итак, d | (8 - 2 (4)) = > d | 0.

Список чисел, делящихся на d, теперь равен 0, 4, 8, 236, 1484, 3084. GCD (a, 0) всегда a. Итак, как только у вас будет 0 в качестве одного из двух чисел, другим номером будет gcd из оригинала два и все те, которые вошли между ними.

Это именно то, что делает ваш код. Вы можете распознать состояние терминала как GCD (a, 0) = a.

Другой шаг - найти оставшуюся часть двух чисел и выбрать это, а меньшее из двух предыдущих - как новые числа.