E003. Euclidean algorithm нахождения НОД (наибольшего общего делителя)

e-maxx algorithm original: C/C++ #algorithm #emaxx #math #number-theory
Văn bản bài toán được dịch từ tiếng Nga theo ngôn ngữ giao diện. Mã không thay đổi.

Источник: e-maxx.ru/algo, страница PDF 12.

given два целых неотрицательных числа

и

. it is required find их наибольший общий делитель, т.е. наибольшее

number, которое является делителем одновременно и

, и

. На английском языке "наибольший общий делитель"

пишется "greatest common divisor", и распространённым его обозначением является :

(здесь символом "

" обозначена делимость, т.е. "

" обозначает "

делит

")

Когда оно из чисел равно нулю, а другое отлично от нуля, их наибольшим общим делителем, согласно определению, будет это второе number. Когда оба числа равны нулю, результат не определён (подойдёт любое бесконечно большое number), мы положим в этом случае наибольший общий делитель равным нулю. Поэтому можно говорить о таком правиле: если одно из чисел равно нулю, то их наибольший общий делитель равен второму числу. Euclidean algorithm, рассмотренный ниже, решает задачу нахождения наибольшего общего делителя двух чисел

и

за

. Данный Thuật toán был впервые описан в книге Евклида "Начала" (около 300 г. до н.э.), хотя, вполне возможно, этот Thuật toán имеет более раннее происхождение.

Thuật toán

Сам Thuật toán чрезвычайно прост и описывается следующей формулой:

Cài đặt

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

else

return gcd (b, a % b);

} Используя тернарный условный оператор C++, Thuật toán можно записать ещё короче:

int gcd (int a, int b) {
return b ? gcd (b, a % b) : a;

} Наконец, приведём и нерекурсивную форму Thuật toánа:

int gcd (int a, int b) {
while (b) {

a %= b;

swap (a, b);

}

return a;

}

Chứng minh tính đúng đắn

Сначала заметим, что при каждой итерации Thuật toánа Евклида его второй аргумент строго убывает, следовательно, посколько он неотрицательный, то Euclidean algorithm всегда завершается.

Для доказательства корректности нам необходимо показать, что

для любых

. Покажем, что величина, стоящая в левой части равенства, делится на настоящую в правой, а стоящая в правой — делится на стоящую в левой. Очевидно, это будет означать, что левая и правая части совпадают, что и докажет корректность Thuật toánа Евклида.

Обозначим

. Тогда, по определению,

и

.

Далее, разложим остаток от деления

на

через их частное: Но тогда отсюда следует:

Итак, вспоминая утверждение

, получаем систему: Воспользуемся теперь следующим простым фактом: если для каких-то трёх чисел выполнено:

и

,

то выполняется и: . В нашей ситуации получаем:

Или, подставляя вместо

его Định nghĩa как

, получаем: Итак, мы провели половину доказательства: показали, что левая часть делит правую. Вторая половина доказательства производится аналогично.

Thời gian chạy

Thời gian chạy Thuật toánа оценивается теоремой Ламе, которая устанавливает удивительную связь Thuật toánа Евклида и последовательности Фибоначчи:

Если

и

для некоторого

, то Euclidean algorithm выполнит не более

рекурсивных вызовов. Более того, можно показать, что верхняя граница этой теоремы — оптимальная. При

будет выполнено именно

рекурсивных вызова. Иными словами, последовательные

Fibonacci numbers — наихудшие Đầu vàoные данные для Thuật toánа Евклида. given, что Fibonacci numbers растут экспоненциально (как константа в степени

), получаем, что Thuật toán

Евклида выполняется за

операций умножения.

НОК (наименьшее общее кратное)

Вычисление наименьшего общего кратного (least common multiplier, lcm) сводится к вычислению

следующим

простым утверждением: Таким образом, вычисление НОК также можно сделать с помощью Thuật toánа Евклида, с той же асимптотикой:

int lcm (int a, int b) {
return a / gcd (a, b) * b;

}

(здесь выгодно сначала поделить на

, а только потом домножать на

, поскольку это поможет избежать

переполнений в некоторых случаях)

References

● Томас Кормен, Чарльз Лейзерсон, Рональд Ривест, Клиффорд Штайн. Thuật toánы: Построение и

анализ [2005]

C# lời giải

bản nháp tự động, xem lại trước khi gửi
using System;
using System.Collections.Generic;
using System.Linq;

public static class AlgorithmDraft
{
    // Auto-generated C# draft from the original e-maxx C/C++ listing. Review before production use.
    int gcd (int a, int b) {
            if (b == 0)
                    return a;
            else
                    return gcd (b, a % b);
    }
    int gcd (int a, int b) {
            return b ? gcd (b, a % b) : a;
    }
    int gcd (int a, int b) {
            while (b) {
                    a %= b;
                    swap (a, b);
            }
            return a;
    }
    int lcm (int a, int b) {
            return a / gcd (a, b) * b;
    }
}

C++ lời giải

đã khớp/gốc
int gcd (int a, int b) {
        if (b == 0)
                return a;
        else
                return gcd (b, a % b);
}
int gcd (int a, int b) {
        return b ? gcd (b, a % b) : a;
}
int gcd (int a, int b) {
        while (b) {
                a %= b;
                swap (a, b);
        }
        return a;
}
int lcm (int a, int b) {
        return a / gcd (a, b) * b;
}

Java lời giải

bản nháp tự động, xem lại trước khi gửi
import java.util.*;
import java.math.*;

public class AlgorithmDraft {
    // Auto-generated Java draft from the original e-maxx C/C++ listing. Review before production use.
    int gcd (int a, int b) {
            if (b == 0)
                    return a;
            else
                    return gcd (b, a % b);
    }
    int gcd (int a, int b) {
            return b ? gcd (b, a % b) : a;
    }
    int gcd (int a, int b) {
            while (b) {
                    a %= b;
                    swap (a, b);
            }
            return a;
    }
    int lcm (int a, int b) {
            return a / gcd (a, b) * b;
    }
}

Материал разбит как Thuật toánическая Bài toán: изучить постановку, понять асимптотику и реализовать Thuật toán на выбранном языке.

Vacancies for this task

việc làm đang hoạt động with overlapping task tags are đã hiển thị.

Tất cả việc làm
Chưa có việc làm đang hoạt động.