GCD에 가장 효율적인 것은 무엇입니까?


26

유클리드의 알고리즘은 양의 정수 목록의 GCD (큰 공통 제수)를 얻는 데 가장 적합한 알고리즘이라는 것을 알고 있습니다. 그러나 실제로이 알고리즘을 다양한 방법으로 코딩 할 수 있습니다. (제 경우에는 Java를 사용하기로 결정했지만 C / C ++가 다른 옵션 일 수 있습니다).

내 프로그램에서 가능한 가장 효율적인 코드를 사용해야합니다.

재귀 모드에서는 다음과 같이 쓸 수 있습니다.

static long gcd (long a, long b){
    a = Math.abs(a); b = Math.abs(b);
    return (b==0) ? a : gcd(b, a%b);
  }

반복 모드에서는 다음과 같습니다.

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

GCD에 대한 이진 알고리즘도 있으며 다음과 같이 간단하게 코딩 할 수 있습니다.

int gcd (int a, int b)
{
    while(b) b ^= a ^= b ^= a %= b;
    return a;
}

3
나는 이것이 너무 주관적이며 아마도 StackOverflow에 더 적합하다고 생각합니다. "실제로 가장 효율적"은 기본 아키텍처, 메모리 계층, 입력의 크기 및 형식 등과 같은 많은 (예측할 수없는) 요소에 따라 달라집니다.
Juho

5
이것은 재귀적이고 반복적 인 방식으로 표현 된 동일한 알고리즘입니다. 유클리드 알고리즘이 매우 빠르게 수렴하기 때문에 그 차이는 무시할 만하다고 생각합니다. 취향에 맞는 것을 선택하십시오.
pad

6
이 두 가지를 프로파일 링하려고 할 수 있습니다. 재귀 버전은 테일 호출이므로 컴파일러가 실제로 거의 동일한 코드를 생성하지는 않습니다.
Louis

1
이것은 잘못이다. b! = 0이어야하고 a를 반환합니다. 그렇지 않으면 0으로 나누면 버그가 발생합니다. 정말 큰 gcd를 가지고 있다면 재귀를 사용하지 마십시오 .... 스택과 함수 상태가 쌓입니다 ... 왜 반복하지 않습니까?
Cris Stringfellow

4
빠른 속도로 GCD 알고리즘이 있습니다. 예 : en.wikipedia.org/wiki/Binary_GCD_algorithm
Neal Young

답변:


21

두 알고리즘은 동일합니다 (적어도 양의 정수의 경우 명령형 버전에서 음의 정수로 발생하는 것은 %내가 알지 못하는 Java의 의미에 따라 다릅니다 ). 재귀 버전에서 및 번째 재귀 호출 의 인수로 설정하십시오 . b i i a i + 1 = b i b i + 1 = a i m o d b iaibii

ai+1=bibi+1=aimodbi

명령형 버전에서하자 및 변수의 값일 수 와 의 처음에 루프 회 반복. B " I I ' I + 1 = B ' ' 나는 + 1 = ' 나는 해요 O D B ' Iaibiabi

ai+1=bibi+1=aimodbi

닮은가? 명령 버전과 재귀 버전은 정확히 동일한 값을 계산합니다. 또한 (예 : ) 일 때 둘 다 동시에 종료 되므로 동일한 반복 횟수를 수행합니다. 알고리즘 적으로 말하면, 둘 사이에는 차이가 없습니다. 차이점은 구현의 문제이며, 컴파일러, 실행되는 하드웨어 및 운영 체제 및 다른 프로그램이 동시에 실행되는 것에 크게 의존합니다.a i = 0ai=0ai=0

재귀 버전은 꼬리 재귀 호출 만 수행 합니다 . 명령형 언어를위한 대부분의 컴파일러는이를 최적화하지 않으므로, 생성하는 코드가 각 반복마다 스택 프레임을 구성하는 데 약간의 시간과 메모리가 낭비 될 수 있습니다. 테일 호출 (함수 언어를위한 컴파일러는 거의 항상)을 최적화하는 컴파일러를 사용하면 생성 된 머신 코드가 두 코드에서 동일 할 수 있습니다 (이러한 호출을로 조정한다고 가정 abs).


8

작은 숫자의 경우 이진 GCD 알고리즘으로 충분합니다.

잘 관리되고 실제 테스트 된 라이브러리 인 GMP는 Lehmer 알고리즘의 일반화 인 특수 임계 값을 통과 한 후 특수 절반 GCD 알고리즘으로 전환됩니다. Lehmer는 행렬 곱셈을 사용하여 표준 유클리드 알고리즘을 개선합니다. 문서에 따르면, HGCD와 GCD의 점근 적 실행 시간은입니다 O(M(N)*log(N)). 여기서 M(N)두 개의 N- 림 숫자를 곱하는 시간입니다.

알고리즘에 대한 자세한 내용은 여기를 참조하십시오 .


이 링크는 실제로 자세한 정보를 제공하지 않으며 "다리"가 무엇인지 정의하지도 않습니다.
einpoklum-복원 Monica Monica


2

알다시피 Java는 일반적으로 꼬리 재귀 최적화를 지원하지 않지만 Java 구현을 테스트 할 수 있습니다. 그것을 지원하지 않으면 간단한 for루프가 더 빠르고 그렇지 않으면 재귀도 빨라야합니다. 반면에, 이것은 비트 최적화이며, 더 쉽고 읽기 쉬운 코드를 선택하십시오.

또한 가장 빠른 GCD 알고리즘은 Euclid의 알고리즘이 아니며 Lehmer의 알고리즘 은 약간 빠릅니다.


당신은 의미합니까 지금까지 내가 아는 한 ? 언어 사양이이 최적화를 요구하지 않거나 (그렇다면 놀랍습니다) 대부분의 구현이이를 구현하지 않는다는 것을 의미합니까?
PJTraill

1

첫째, 타이트한 루프를 대체하기 위해 재귀를 사용하지 마십시오. 느립니다. 컴파일러를 사용하여 최적화하지 마십시오. 둘째, 코드에서 모든 재귀 호출 내에서 Math.abs ()를 호출하면 쓸모가 없습니다.

루프에서 임시 변수를 피하고 a와 b를 항상 바꾸는 것을 쉽게 피할 수 있습니다.

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

a ^ = b ^ = a ^ = b를 사용하여 스와핑하면 소스가 짧아 지지만 많은 명령이 실행됩니다. 임시 변수가있는 보링 스왑보다 느립니다.


3
“재귀를 피하십시오. 속도가 느리다”— 일반적인 조언으로 제시되는데 이것은 가짜입니다. 컴파일러에 따라 다릅니다. 일반적으로 재귀를 최적화하지 않는 컴파일러의 경우에도 속도가 느려지지 않고 스택을 소비합니다.
Gilles 'SO- 악의

3
그러나 이와 같은 짧은 코드의 경우 차이점이 중요합니다. 스택 소비는 메모리에 쓰고 읽는 것을 의미합니다. 느리다. 위의 코드는 2 개의 레지스터에서 실행됩니다. 재귀는 또한 조건부 점프보다 긴 통화를 의미합니다. 재귀 호출은 분기 예측에 훨씬 어렵고 인라인하기가 어렵습니다.
Florian F

-2

들어 작은 숫자 , %는 아마도 간단한 재귀 매우 비용이 많이 드는 작업이다

GCD[a,b] := Which[ 
   a==b , Return[a],
   b > a, Return[ GCD[a, b-a]],
   a > b, Return[ GCD[b, a-b]]
];

더 빠릅니까? (죄송합니다, Mathematica 코드이며 C ++ 아님)


제대로 보이지 않습니다. b == 1 인 경우 1을 반환하고 GCD [2,1000000000]이 느려집니다.
Florian F

아, 그렇습니다. 고정되어 있다고 생각합니다.
Per Alexandersson

일반적으로 GCD [a, 0]도 a를 반환해야합니다. 당신은 영원히 반복됩니다.
Florian F

귀하의 답변에 코드 만 포함되어 있으므로 다운 투표하고 있습니다. 우리는이 사이트에 대한 아이디어에 집중하고 싶습니다. 예를 들어, 왜 %가 비싼 작업입니까? 내 생각에 코드 조각에 대한 추측은 실제로이 사이트에 대한 좋은 대답이 아닙니다.
Juho

1
모듈러스가 빼기보다 느리다는 생각은 민속으로 간주 될 수 있다고 생각합니다. 작은 정수 (일반적으로 빼기에는 한주 기가 걸리고 모듈로는 거의 사용하지 않음)와 큰 정수 (빼기는 선형입니다)는 모듈로에 대한 최고의 복잡성이 무엇인지 확실하지 않지만 그보다 확실히 나쁩니다. 물론 필요한 반복 횟수도 고려해야합니다.
질 'SO-정지 존재 악마'

-2

유클리드 알고리즘은 GCD 계산에 가장 효율적입니다.

정적 긴 gcd (long a, long b)
{
if (b == 0)
a를 반환;
그밖에
리턴 gcd (, a % b);
}

예:-

A = 16, B = 10으로 둡니다.
GCD (16, 10) = GCD (10, 16 % 10) = GCD (10, 6)
GCD (10, 6) = GCD (6, 10 % 6) = GCD (6, 4)
GCD (6, 4) = GCD (4, 6 % 4) = GCD (4, 2)
GCD (4, 2) = GCD (2, 4 % 2) = GCD (2, 0)


B = 0이므로 GCD (2, 0)은 2를 반환합니다. 

4
이것은 질문에 대답하지 않습니다. 이 asker는 두 가지 버전의 Euclid를 제공하며 더 빠른 버전을 묻습니다. 당신은 그것을 알지 못하고 재귀 버전을 유클리드의 유일한 알고리즘으로 선언하고 다른 모든 것보다 빠르다는 증거가 없다고 주장합니다.
David Richerby
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.