유클리드 호제법이란
유클리드 호제법 (Euclidean algorithm)은 두 자연수(또는 정수)의 최대공약수(Greatest Common Divisor, GCD)를 효율적으로 구하기 위한 알고리즘입니다. 기원전 300년경 고대 그리스의 수학자 유클리드가 저술한 수학서 『원론』(Elements) 제7권에 기록되어 있으며, “인류 최고(最古)의 알고리즘” 중 하나로도 널리 알려져 있습니다.
최대공약수를 구하는 가장 단순한 방법은 두 수를 각각 소인수분해하여 공통된 소인수를 곱하는 것이지만, 수가 커지면 소인수분해 자체의 계산량이 방대해져 현실적인 시간 내에 풀기 어려워집니다. 반면, 유클리드 호제법 을 사용하면 수천 자리에 달하는 거대한 수끼리라도 매우 빠르게 최대공약수를 계산할 수 있습니다.
기본 정리와 원리
두 자연수 $a$ 와 $b$ ($a \ge b$)의 최대공약수를 $\gcd(a, b)$ 로 나타냅니다. 유클리드 호제법은 다음의 단순한 정리에 기초하고 있습니다.
$$ a = bq + r \implies \gcd(a, b) = \gcd(b, r) $$즉, “$a$ 를 $b$ 로 나누었을 때의 몫을 $q$ , 나머지를 $r$ 이라고 할 때, $a$ 와 $b$ 의 최대공약수는 $b$ 와 $r$ 의 최대공약수와 같다"는 성질입니다.
정리의 증명
왜 $\gcd(a, b) = \gcd(b, r)$ 이 성립할까요? 간단히 증명해 보겠습니다.
- $a$ 와 $b$ 의 임의의 공약수를 $d$ 라고 합니다. 이때 $a = md, b = nd$ ($m, n$ 은 정수)로 나타낼 수 있습니다.
- $a = bq + r$ 에서 $r = a - bq$ 가 됩니다.
- 여기에 대입하면 $r = md - (nd)q = d(m - nq)$ 가 됩니다.
- $m - nq$ 는 정수이므로 $d$ 는 $r$ 의 약수이기도 합니다. 따라서 $a$ 와 $b$ 의 공약수 $d$ 는 $b$ 와 $r$ 의 공약수이기도 합니다.
- 반대로 $b$ 와 $r$ 의 공약수를 $e$ 라고 하면 $b = k e, r = l e$ 로 나타낼 수 있습니다.
- $a = bq + r = (k e)q + l e = e(kq + l)$ 이 되어 $e$ 는 $a$ 의 약수가 됩니다. 따라서 $b$ 와 $r$ 의 공약수 $e$ 는 $a$ 와 $b$ 의 공약수이기도 합니다.
- 결론적으로 $\{a, b\}$ 의 공약수 집합과 $\{b, r\}$ 의 공약수 집합은 완전히 일치하며, 그 최댓값인 최대공약수도 같아집니다. $\blacksquare$
알고리즘 순서도
이 성질을 이용하여 나머지가 $0$ 이 될 때까지 나눗셈을 반복하는 것이 유클리드 호제법입니다.
flowchart TD
Start["시작: a, b 를 입력"] --> Check{"b == 0 ?"}
Check -- "Yes" --> End["GCD 는 a"]
Check -- "No" --> Calc["r = a % b"]
Calc --> Update["a = b, b = r"]
Update --> Check
구체적인 계산 과정의 예
예를 들어 $a = 1071$ 과 $b = 1029$ 의 최대공약수를 구해 봅시다.
- $1071 \div 1029 = 1 \cdots 42$ ($a=1029, b=42$ 로 갱신)
- $1029 \div 42 = 24 \cdots 21$ ($a=42, b=21$ 로 갱신)
- $42 \div 21 = 2 \cdots 0$ (나머지가 $0$ 이 되었으므로 종료)
마지막으로 나누는 수로 남은 $21$ 이 $1071$ 과 $1029$ 의 최대공약수입니다.
프로그램 구현
Python 구현
Python에서는 재귀 함수를 사용하는 방법과 while 루프를 사용하는 방법이 있습니다. 루프를 사용하는 편이 함수 호출의 오버헤드가 없어 더 빠릅니다.
| |
C++ 구현
C++17 이후에서는 <numeric> 헤더에 std::gcd 가 표준 구현되어 있지만, 직접 구현할 경우 다음과 같이 작성할 수 있습니다.
| |
계산 복잡도와 라메의 정리
유클리드 호제법은 얼마나 빠를까요? 이 계산 복잡도에 대해서는 1844년 프랑스 수학자 가브리엘 라메가 증명한 라메의 정리 (Lamé’s theorem)가 유명합니다.
라메의 정리 두 자연수 $a, b$ ($a > b$)에 대해 유클리드 호제법을 적용했을 때의 나눗셈 횟수는 $b$ 의 십진법 자릿수의 $5$ 배 이하이다.
이에 따라 알고리즘의 시간 복잡도는 $O(\log(\min(a, b)))$ 가 됩니다.
최악의 경우(나눗셈 횟수가 가장 많아지는 경우)는 피보나치 수열의 인접한 두 항이 주어졌을 때입니다. 예를 들어 $F_{n+2}$ 와 $F_{n+1}$ 의 최대공약수를 구하는 과정은 항상 몫이 $1$ 이 되며 차례로 더 작은 피보나치 수로 옮겨가게 됩니다.
확장 유클리드 호제법
최대공약수를 구하는 것뿐만 아니라, 아래의 베주 항등식(Bézout’s identity)을 만족하는 정수 $x, y$ 를 구하는 알고리즘으로 확장한 것을 확장 유클리드 호제법 (Extended Euclidean algorithm)이라고 부릅니다.
$$ ax + by = \gcd(a, b) $$확장 유클리드 호제법 구현
재귀 호출에서 돌아오는 과정에서 $x, y$ 의 계수를 역산해 나갑니다.
| |
현대 사회에서의 응용 (RSA 암호 등)
확장 유클리드 호제법은 단순한 수학 퍼즐이 아니라, 현대의 인터넷 사회를 지탱하는 필수 불가결한 기술입니다. 대표적인 예가 RSA 암호 입니다. RSA 암호의 키 생성 과정에서는 어떤 수 $e$ 와 오일러 피 함수 $\phi(N)$ 에 대해 $e d \equiv 1 \pmod{\phi(N)}$ 을 만족하는 비밀키 $d$ (모듈로 역원)를 구해야 합니다. 이는 $ed + k\phi(N) = 1$ 형태로 변형할 수 있으므로, 바로 확장 유클리드 호제법을 이용하여 빠르게 $d$ 를 계산할 수 있습니다.
요약
유클리드 호제법은 기원전이라는 아득한 옛날에 발견되었음에도 불구하고 그 군더더기 없는 논리와 계산 효율성 덕분에 현대 컴퓨터 과학의 근간을 계속해서 지탱하고 있습니다. 알고리즘을 배울 때 가장 먼저 접하게 되는 주제인 경우가 많지만, 그 이면에는 수학적인 아름다움과 실용성이 가득 담겨 있습니다.
