두 정수 a,b공약수a의 약수이자 b의 약수인 정수입니다.

최대공약수(GCD, Great Common Divisor)가장 큰 공약수를 말합니다.

 

두 정수 a,b공배수ab 모두의 배수가 되는 정수입니다.

최소공배수(LCM, Least Common Multiple)가장 작은 공배수를 말합니다.

 

 

 

최대공약수, 최소공배수의 성질

GCD(a,b,c)=GCD(GCD(a,b),c)LCM(a,b,c)=LCM(LCM(a,b),c)

세 수, 그 이상 N개의 숫자의 최대공약수, 최소공배수는 위와 같은 식으로 구할 수 있습니다.

 

 

 

최대공약수 구하는 방법

(1) 2부터 min(a,b)까지 나눠보기 (Brute Force)

int gcd = 1;
for (int i=2; i<=min(a,b); ++i)
    if (a%i==0 && b%i==0)
        gcd = i;

가장 쉬운 방법이지만 O(min(a,b))의 시간복잡도를 가져 비효율적입니다.

 

(2) 소인수분해 (prime factorization)

두 수를 소인수분해 해서 중복되는 부분을 찾으면 최대공약수가 됩니다.

예를 들어 a=192,b=72라고 하면

192=26×372=23×32GCD(192,72)=23×3=24

이런 식으로 구할 수 있습니다.

문제는 소인수분해를 하는게 번거롭고 느리다는 겁니다.

 

(3) 유클리드 호제법 (Euclidean algorithm)

ab로 나눈 나머지를 r이라고 했을 때, 즉 a이라고 했을 때, GCD(a,b)=GCD(b,r)임을 이용하면 빠르게 구할 수 있습니다. 나머지가 0이 될 때까지 진행하면 최대공약수를 구할 수 있습니다.

 

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

재귀함수를 사용하면 위와 같이 나타낼 수 있고요, Tail Recursion이기 때문에 반복문으로 바꿀 수 있습니다. 재귀 버전은 호출스택이 쌓이기 때문에 메모리를 더 씁니다.

 

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

반복문으로 만든 최대공약수 함수입니다. 시간복잡도는 O(log(a+b))라고 하네요.

참고로 a>b일때 swap 하도록 하는 사람도 있는데 굳이 그렇게 하지 않아도 알아서 바뀝니다. 이건 숫자 대입해보면 바로 알 수 있습니다.

 

 

 

최소공배수 구하는 방법

최소공배수는 위에서 구한 최대공약수를 활용해 구할 수 있습니다.

LCM(a,b)=a×bGCD(a,b)

두 수의 곱을 최대공약수로 나눠주면 최소공배수가 됩니다.

 

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

위 공식을 그대로 옮긴 함수입니다. 문제가 없어보이지만 a×b 부분에서 오버플로우가 발생하기 쉽습니다.

예를 들어 lcm(50000,100000)을 계산해보면 당연히 반환값이 100000이 나와야겠지만, 대신 14100이라는 값이 반환됩니다. a×b의 값이 50억으로 32비트 정수형의 범위를 넘어가기 때문입니다.

 

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

이렇게 미리 한쪽을 gcd로 나눠주면 오버플로우가 발생하지 않습니다.

 

 

 

(참고) 라이브러리 사용 방법

(1) gcc __gcd

gcc 내장 함수중에 최대공약수를 구할 수 있는 함수가 있습니다. (stl_algo.h)

template<typename _EuclideanRingElement>
    _EuclideanRingElement
    __gcd(_EuclideanRingElement __m, _EuclideanRingElement __n)
    {
        while (__n != 0)
        {
            _EuclideanRingElement __t = __m % __n;
            __m = __n;
            __n = __t;
        }
        return __m;
    }

유클리드 호제법으로 구현돼있습니다. 비표준이니 다른 컴파일러에는 __gcd 함수가 없을 수도 있습니다.

 

(2) (c++17 이상) std::gcd, std::lcm

c++17부터 <numeric> 헤더에 gcd, lcm 함수가 추가됐습니다. 구현 방식은 똑같이 유클리드 호제법으로 되어있습니다.

 

반응형

'수학 > 정수론' 카테고리의 다른 글

약수의 개수, 약수의 합  (0) 2020.07.03
소수를 구하는 방법  (0) 2020.04.09
음수의 모듈러 연산  (0) 2020.04.09
큰 수의 모듈러 연산  (0) 2020.03.24