본문으로 건너뛰기
김신건의 로그

유클리드 호제법 (Euclidean Algorithm)

· 수정 · 📖 약 2분 · 787자/단어 #algorithm #math #euclidean #number-theory #gcd
euclidean algorithm, 유클리드 호제법, gcd, euclidean

정의

유클리드 호제법 (Euclidean Algorithm) 은 두 정수 a, b 의 최대공약수 (GCD, Greatest Common Divisor) 를 O(log min(a, b)) 시간에 구하는 알고리즘. 기원전 300년경 유클리드 원론 7권에 등장. 재귀식 gcd(a, b) = gcd(b, a mod b) 가 핵심.

문제 상황과 동기

a, b 의 최대공약수를 구하라 (a, b ≤ 10^18).

  • naive (1..min(a,b) 순회): O(min(a, b)). a=10^18 면 timeout.
  • 소인수분해 후 공통 인수: O(√a + √b). 여전히 큰 수에 느림.
  • 유클리드 호제법: O(log min(a, b)). 피보나치 수열이 최악 케이스, 최대 log₂ 단계.

핵심 통찰: gcd(a, b) = gcd(b, a mod b). 나머지 연산으로 숫자가 빠르게 줄어듦.

시각화

핵심 아이디어

나머지 연산과 GCD

a = b·q + r  (0 ≤ r < b)
gcd(a, b) = gcd(b, r)

증명:
  d = gcd(a, b) 이면, d | a, d | b.
  a = b·q + r 이므로 r = a - b·q, 따라서 d | r.
  즉 d 는 b, r 의 공약수.
  
  역으로 d' = gcd(b, r) 이면, d' | b, d' | r.
  a = b·q + r 이므로 d' | a.
  즉 d' 는 a, b 의 공약수.
  
  따라서 gcd(a, b) = gcd(b, r).

재귀 종료

gcd(a, 0) = a
gcd(a, b) = gcd(b, a mod b)

b 가 0 이 될 때까지 반복. 최악 O(log min(a, b)) 단계.

확장 유클리드 (Extended Euclidean)

gcd(a, b) = d 뿐 아니라, 베주 항등식 (Bézout’s identity) a·x + b·y = d 를 만족하는 정수 x, y 도 구함.

extgcd(a, b):
    if b == 0: return (a, 1, 0)  // gcd, x, y
    d, x', y' = extgcd(b, a mod b)
    x = y'
    y = x' - (a // b) * y'
    return (d, x, y)

mod 역원 계산 (a·x ≡ 1 (mod m) 의 x) 에 필수.

알고리즘

재귀 (간결)

gcd(a, b):
    if b == 0: return a
    return gcd(b, a mod b)

반복 (스택 X)

gcd(a, b):
    while b != 0:
        a, b = b, a mod b
    return a

확장 유클리드

extgcd(a, b):
    if b == 0: return (a, 1, 0)
    d, x1, y1 = extgcd(b, a % b)
    x = y1
    y = x1 - (a // b) * y1
    return (d, x, y)

구현

// 재귀 + 반복 gcd, extgcd
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

ll gcd_recursive(ll a, ll b) {
  return b ? gcd_recursive(b, a % b) : a;
}

ll gcd_iterative(ll a, ll b) {
  while (b) {
      ll t = a % b;
      a = b;
      b = t;
  }
  return a;
}

// Extended Euclidean: ax + by = gcd(a, b)
tuple<ll, ll, ll> extgcd(ll a, ll b) {
  if (b == 0) return {a, 1, 0};
  auto [d, x1, y1] = extgcd(b, a % b);
  return {d, y1, x1 - (a / b) * y1};
}

int main() {
  ll a, b; cin >> a >> b;
  cout << "gcd(rec): " << gcd_recursive(a, b) << "\n";
  cout << "gcd(iter): " << gcd_iterative(a, b) << "\n";
  auto [d, x, y] = extgcd(a, b);
  cout << "extgcd: " << d << ", x=" << x << ", y=" << y << "\n";
  cout << "check: " << a << "*" << x << " + " << b << "*" << y << " = " << a*x + b*y << "\n";
}
stdin
48 18
결과
gcd(rec): 6
gcd(iter): 6
extgcd: 6, x=1, y=-2
check: 48*1 + 18*-2 = 6

복잡도

항목
시간 (최악)O(log min(a, b))
공간 (재귀)O(log min(a, b)) 스택
공간 (반복)O(1)

최악 케이스: 연속된 피보나치 수 (F_{n+1}, F_n). n 단계 필요, n ≈ log φ · min(a, b).

변형

방법특징용도
Binary GCD (Stein)비트 연산, mod X하드웨어 최적화
Extended Euclidean베주 항등식 ax + by = dmod 역원, 중국인 나머지
Lehmer GCD큰 정수 (multi-precision)GMP, BigInteger
LCM (최소공배수)lcm(a, b) = a·b / gcd(a, b)배수 문제

함정

1. 0 처리

gcd(0, b) = b, gcd(a, 0) = a. 둘 다 0 이면 정의 X (보통 0 또는 에러).

2. 음수

gcd(-a, b) = gcd(a, b). 구현에서 abs() 처리 또는 mod 가 항상 양수 보장.

3. 오버플로우 (extgcd)

a·x + b·y 계산 시 중간 곱 a·x 가 long long 초과 가능. Python 은 자동 bigint, C++/Java 는 조심.

4. lcm 오버플로우

lcm(a, b) = a / gcd(a, b) * b 순서로. a * b / gcd 는 중간 곱 오버플로우.

BOJ 연습 문제

번호제목정답률링크
BOJ 2609최대공약수와 최소공배수-kokoa-lab
BOJ 1850최대공약수-kokoa-lab
BOJ 3036-kokoa-lab
BOJ 14565역원(Inverse) 구하기-kokoa-lab

참고

이 글의 용어 (4개)
모듈러 역원 (Modular Multiplicative Inverse)algorithm
정의 정수 a 의 모듈러 역원 (modular multiplicative inverse) 은 을 만족하는 정수 x. 기호로 또는 . 존재 조건: gcd(a, m) = 1 일 때만…
소수 판정 (Primality Test)algorithm
정의 소수 판정 (Primality Test) 은 주어진 정수 N이 소수인지 합성수인지 판별하는 알고리즘. 시행 나눗셈 (Trial Division) O(√N), 확률적 Ferm…
소인수분해 (Prime Factorization)algorithm
정의 소인수분해 (Prime Factorization) 는 양의 정수 N을 소수들의 곱으로 유일하게 나타내는 것. N = p₁^a₁ · p₂^a₂ · ... · pₖ^aₖ. 시행…
중국인의 나머지 정리 (Chinese Remainder Theorem)algorithm
정의 중국인의 나머지 정리 (CRT) 는 다음과 같은 연립 합동식의 해가 유일 하게 존재함을 보장: 단, m1, m2, ..., mk 는 쌍마다 서로소 (pairwise copr…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기