맨위로가기

에라토스테네스의 체

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

에라토스테네스의 체는 주어진 범위 내에서 소수를 찾는 알고리즘으로, 소수가 아닌 수들을 지워나가는 방식으로 작동한다. 2부터 시작하여 각 소수의 배수를 제거하는 과정을 반복하며, 120까지의 소수를 찾을 때는 11보다 작은 수의 배수만 제거해도 충분하다. 이 알고리즘은 의사 코드로 표현될 수 있으며, 시간 복잡도는 O(n log log n)이다. 에라토스테네스의 체는 분할 체, 증분 체, 오일러의 체 등 다양한 변형이 존재하며, C++, Java, Python, Haskell 등 다양한 프로그래밍 언어로 구현될 수 있다.

더 읽어볼만한 페이지

  • 소수 판별법 - 밀러-라빈 소수판별법
    밀러-라빈 소수판별법은 페르마 소정리를 기반으로 주어진 수가 소수인지 판별하는 확률적 알고리즘으로, 합성수를 소수로 잘못 판별할 가능성이 있지만 검사를 반복할수록 정확도가 높아지며, 일반화 리만 가설이 참일 경우 결정론적 알고리즘으로 전환될 수 있다.
  • 소수 판별법 - 베일리–PSW 소수판별법
    베일리-PSW 소수판별법은 초기 나눗셈, 밀러-라빈 소수판별법, 특정 조건에 따른 D, P, Q 값 선택, 강한 뤼카 소수판별법의 단계를 거쳐 주어진 정수의 소수 여부를 판별하는 알고리즘으로, 페르마 소수성 검정과 뤼카 소수성 검정을 결합하여 여러 소프트웨어에서 활용된다.
에라토스테네스의 체
개요
에라토스테네스의 체 애니메이션
에라토스테네스의 체 애니메이션
유형소수 생성 알고리즘
발견자에라토스테네스
발견 시기기원전 3세기
상세 정보
용도지정된 범위 내의 모든 소수 찾기
시간 복잡도O(n log log n)
공간 복잡도O(n)

2. 알고리즘

소수는 1과 자기 자신만을 약수로 갖는 자연수이다. 에라토스테네스의 체는 주어진 정수 ''n''보다 작거나 같은 모든 소수를 찾는 효율적인 알고리즘이다.

알고리즘의 기본적인 작동 원리는 다음과 같다.

# 2부터 ''n''까지의 모든 정수를 순서대로 나열한다.

# 목록의 가장 작은 수(처음에는 2)를 소수로 간주하고, 그 수를 제외한 해당 수의 모든 배수를 목록에서 제거한다.

# 목록에 남아있는 수 중 다음으로 가장 작은 수를 선택하여 소수로 간주하고, 다시 그 수를 제외한 해당 수의 모든 배수를 제거한다.

# 이 과정을 현재 선택한 소수 ''p''의 제곱(''p''2)이 ''n''보다 커질 때까지 반복한다.

과정이 끝나면 목록에 남아있는 모든 수가 ''n'' 이하의 소수가 된다. 핵심 아이디어는 각 단계에서 선택되는 가장 작은 수 ''p''는 항상 소수라는 점이다. 만약 ''p''가 합성수라면, 그보다 작은 소수의 배수이므로 이전 단계에서 이미 제거되었을 것이기 때문이다.

오른쪽의 애니메이션은 2부터 120까지의 소수를 찾는 과정을 시각적으로 보여준다.

알고리즘의 효율성을 높이기 위한 몇 가지 최적화 방법이 존재한다. 예를 들어, 소수 ''p''의 배수를 제거할 때 ''p''2부터 시작하거나,[1] 초기 목록에서 짝수를 제외하고 홀수만 고려하거나,[1][3] 휠 인수분해 기법을 사용할 수 있다.[6]

에라토스테네스의 체는 다음과 같은 의사 코드로 표현될 수 있다:[7][8]

```pascal

알고리즘 에라토스테네스의 체 는

입력: 정수 n > 1.

출력: 2부터 n까지의 모든 소수.

let A는 불리언 값의 배열로, 정수 2부터 n까지를 인덱스로 사용하며,

초기에는 모두 true로 설정된다.

for i = 2, 3, 4, ..., √n 을 넘지 않을 때 do

if A[i] is true

for j = i*i, i*i+i, i*i+2i, i*i+3i, ..., n을 넘지 않을 때 do

set A[j] := false

return A[i]가 true인 모든 i.

```

이 알고리즘은 ''n'' 이하의 모든 소수를 생성하며, 각 소수 ''i''의 배수를 ''i''2부터 제거하는 최적화가 포함되어 있다. 이 알고리즘의 시간 복잡도는 배열 업데이트가 상수 시간(''O''(1))에 이루어진다고 가정할 때 일반적으로 ''O''(''n'' log log ''n'')이다.

2. 1. 단계별 설명

에라토스테네스의 체를 이용하여 특정 범위 내의 모든 소수를 찾는 과정은 다음과 같다.

# 2부터 소수를 구하고자 하는 상한값까지의 모든 정수를 나열한다.

# 목록의 가장 처음 수인 2는 소수이므로 이를 따로 기록해 둔다.

# 목록에서 2의 배수(4, 6, 8, ...)를 모두 제거한다.

# 목록에서 남아있는 다음 수인 3은 소수이다. 이를 기록해 둔다.

# 목록에서 3의 배수(6, 9, 12, ...)를 모두 제거한다. (이미 지워진 수도 있다.)

# 목록에서 남아있는 다음 수인 5는 소수이다. 이를 기록해 둔다.

# 목록에서 5의 배수(10, 15, 20, ...)를 모두 제거한다.

# 이 과정을 계속 반복한다. 즉, 목록에 남아있는 가장 작은 수를 찾아 기록하고, 그 수의 배수들을 목록에서 제거한다.

# 언제까지 반복하는가? 현재 확인하는 소수 p에 대해, p^2이 소수를 찾고자 하는 상한값보다 커지면 과정을 중단한다. 예를 들어 120까지의 소수를 찾는다면, 7 다음 소수는 11인데, 11^2 = 121은 120보다 크므로 7의 배수까지만 제거하면 된다.

# 과정이 끝나면 목록에 남아있는 모든 수가 해당 범위 내의 소수이다.

예를 들어, 30보다 작거나 같은 모든 소수를 찾으려면 다음과 같이 진행한다.

  • 1단계: 2부터 30까지의 정수를 나열한다.

:: 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

  • 2단계: 2는 소수이다. 목록에서 2의 배수(4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30)를 제거한다.

:: 2 3 5 7 9 11 13 15 17 19 21 23 25 27 29

  • 3단계: 다음 남은 수 3은 소수이다. 목록에서 3의 배수(9, 15, 21, 27)를 제거한다. (6, 12, 18, 24, 30은 이미 제거됨)

:: 2 3 5 7 11 13 17 19 23 25 29

  • 4단계: 다음 남은 수 5는 소수이다. 목록에서 5의 배수(25)를 제거한다. (10, 15, 20, 30은 이미 제거됨)

:: 2 3 5 7 11 13 17 19 23 29

  • 5단계: 다음 남은 수 7은 소수이다. 7의 배수(14, 21, 28)는 이미 모두 제거되었다. 다음 소수는 11인데, 11^2 = 121은 30보다 크므로 과정을 멈춘다.

  • 결과: 목록에 남아있는 수 2, 3, 5, 7, 11, 13, 17, 19, 23, 29가 30 이하의 모든 소수이다.


오른쪽의 GIF 애니메이션은 2부터 120까지의 수에서 소수를 찾는 과정을 보여준다. 이 경우, 7^2 = 49이고 11^2 = 121 > 120이므로, 7의 배수까지만 제거하면 120 이하의 모든 소수를 찾을 수 있다. 즉, 2, 3, 5, 7의 배수를 지우고 남는 수는 모두 소수이다.

2. 2. 최적화

에라토스테네스의 체 알고리즘의 효율성을 높이기 위한 몇 가지 최적화 방법이 있다.

첫 번째 방법은 소수 ''p''의 배수를 제거할 때, ''p''2부터 시작하는 것이다.[1] 이는 ''p''보다 작은 소수 ''k''에 대해, ''k'' × ''p'' 형태의 배수들은 이미 ''k''의 배수를 제거하는 과정에서 지워졌기 때문이다. 따라서 ''p''2 미만의 ''p''의 배수는 고려할 필요가 없다. 이 방법을 사용하면, 알고리즘은 검사하는 소수 ''p''의 제곱(''p''2)이 주어진 상한선 ''n''보다 커지는 시점에서 종료될 수 있어 계산량을 줄일 수 있다.[1]

두 번째 방법은 처음부터 짝수를 제외하고 홀수만 고려하는 것이다.[1][3] 즉, 초기 목록을 2를 제외한 홀수인 (3, 5, ..., ''n'')으로 구성하고, 소수 ''p''의 배수를 제거할 때도 ''p''2부터 시작하여 2''p''씩 건너뛰며 홀수 배수만을 제거한다. 이 방법은 처리해야 할 숫자의 개수를 절반 가까이 줄여준다. 이 방식은 에라토스테네스가 사용했던 원래 알고리즘에도 나타나는 것으로 알려져 있다.[1][3]

더 나아가, 휠 인수분해 기법을 사용하여 최적화할 수도 있다.[6] 이 방법은 2뿐만 아니라 처음 몇 개의 작은 소수들(예: 2, 3, 5)의 배수를 미리 제거한 목록을 사용하여 시작한다. 즉, 초기 목록에는 이 작은 소수들과 상호소수인 숫자들만 포함시킨다. 배수를 제거하는 과정에서도 해당 소수들의 배수가 생성되지 않도록 조정된 간격으로 수를 건너뛰며 제거한다. 이를 통해 더 많은 합성수를 초기에 효율적으로 건너뛸 수 있다.[6]

3. 이론적 배경

에라토스테네스의 는 특정 수 ''x'' 이하의 소수를 찾을 때, ''x''의 제곱근 (x^{1/2}) 이하의 소수들을 미리 알고 있다면, 이 소수들의 배수를 ''x'' 이하의 정수 목록에서 제거하는 방식에 이론적 기반을 둔다. 즉, x^{1/2} 이하 소수의 배수가 아닌 수들만 남기는 '체질' 과정을 통해 x^{1/2}보다 크고 ''x'' 이하인 소수를 결정할 수 있다는 원리이다.

이 방법의 이론적 배경에는 포함-배제의 원리가 있다. 이 원리를 적용하면 ''x'' 이하의 소수 개수 \pi(x)에 대한 수학적 공식을 유도할 수 있다. 구체적으로, ''z'' 이하의 모든 소수의 곱을 P=P(z)라 하면, \sqrt{n} 이하의 소수 배수를 제외하고 남는 \sqrt{n} 초과 ''n'' 이하 소수의 개수는 뫼비우스 함수 \mu(d)를 이용하여 다음과 같이 표현될 수 있다.

:\pi(n)-\pi \left(\sqrt{n}\, \right)=\sum_{d\mid P(\sqrt{n})} \! \mu(d)(\left[\frac{\,n\,}{d}\right]-\left[\frac{\sqrt{n}}{d}\right])

르장드르는 이를 더욱 일반화하여, 임의의 정수 집합 ''A''에서 특정 소수들의 배수를 모두 제거했을 때 남는 원소의 개수를 계산하는 공식을 제시했다. 이를 르장드르의 체라고도 부른다. ''z'' 이하의 모든 소수의 곱을 P(z)라고 할 때, 집합 ''A''에서 ''z'' 이하 소수의 배수를 제외하고 남은 원소의 개수 S(A,P)는 다음과 같이 나타낼 수 있다.

:S(A,P)=\sum_{d\mid P(z)}\!\mu(d) \left| A_{d\,} \right|

여기서 A_d는 집합 ''A''의 원소 중에서 ''d''로 나누어 떨어지는 수들의 집합을 의미한다.

이러한 이론적 접근을 통해 소수의 분포에 관한 중요한 성질을 증명할 수 있다. 예를 들어, 위 공식을 이용하여 충분히 큰 수 ''x''에 대해 \pi(x)의 상한을 추정하고(\pi(n) \, \leq \, \pi(z) + n\prod_{p\leq z} \left( 1 - \frac{1}{\,p\,} \right) + 2^{\pi(z)}), 결과적으로 \lim_{x\to \infin}\! \frac{\,\pi (x)\,}{x} = 0임을 보일 수 있다. 이는 수가 커짐에 따라 전체 자연수 중에서 소수가 차지하는 비율이 점차 감소하여 0에 수렴한다는 것을 의미한다.

그러나 포함-배제의 원리에만 의존하는 방식은 소수 개수를 추정할 때 오차항(2^{\pi(z)}와 같은 항)이 상당히 커지는 본질적인 한계를 가진다. 이러한 어려움을 극복하고 더 정확하게 소수의 분포를 연구하기 위해 현대적인 체 방법이 개발되었다. 현대의 체 방법은 쌍둥이 소수 추측과 같은 수론 분야의 중요한 미해결 문제들을 다루는 데 핵심적인 도구로 널리 응용되고 있다.

3. 1. 수학적 원리

소수는 1과 자기 자신만을 약수로 가지는 자연수이다.[5] 에라토스테네스의 체는 주어진 어떤 수 ''n''까지의 모든 소수를 찾는 방법이다. 이 방법은 2부터 ''n''까지의 정수를 나열한 뒤, 가장 작은 소수인 2부터 시작하여 그 배수들을 모두 지워나가는 방식으로 작동한다. 그 다음, 남아있는 수 중 가장 작은 수(다음 소수, 예를 들어 3)를 선택하고 다시 그 배수들을 지워나간다. 이 과정을 반복하면 합성수는 모두 지워지고 소수만 남게 된다.

이 방법의 핵심 원리는 어떤 수 ''p''의 배수를 제거할 때, 그 ''p''는 항상 소수라는 점이다. 만약 ''p''가 합성수라면, 그보다 작은 소수의 배수로서 이미 이전 단계에서 지워졌을 것이기 때문이다.[1] 예를 들어, 15는 3의 배수를 지울 때와 5의 배수를 지울 때 모두 표시될 수 있지만, 소수인 3이나 5 자체는 지워지지 않는다.

알고리즘의 효율성을 높이기 위한 개선 방법도 존재한다. 각 소수 ''p''의 배수를 지울 때, ''p'' × 2, ''p'' × 3, ... 순서로 지우는 대신 ''p''2부터 시작하여 지워나가도 충분하다. 왜냐하면 ''p''2보다 작은 ''p''의 배수(예: ''p'' × ''k'' 에서 ''k'' < ''p'')는 이미 ''k''가 소수이거나 ''k''의 소인수의 배수로서 이전 단계에서 지워졌기 때문이다. 이 개선을 적용하면, 알고리즘은 ''p''2이 ''n''보다 커지는 시점에서 종료될 수 있다. 이는 소수를 찾을 범위를 ''n''의 제곱근까지만 고려해도 된다는 의미와 상통한다.[1] 또 다른 개선 방법으로는 처음부터 2의 배수인 짝수를 제외하고 홀수만 나열한 뒤, 3 이상의 소수 ''p''에 대해 그 홀수 배수들(3''p'', 5''p'', ...)만 지워나가는 방식이 있다. 이는 계산량을 줄이는 데 도움이 된다.[1][3] 휠 인수분해를 이용하면 이를 더 일반화할 수 있는데, 처음 몇 개의 작은 소수와 상호소수인 수들만 초기 목록으로 사용하고, 그 배수들만 제거하도록 조정된 간격으로 계산하는 방식이다.[6]

에라토스테네스의 체는 수학적으로 포함-배제의 원리와 깊은 관련이 있다. 어떤 수 ''n''까지의 정수 집합에서, ''n''의 제곱근 이하의 소수들의 배수를 순차적으로 제거해 나가면 결국 ''n'' 이하의 소수만 남게 된다. 이 원리를 이용하여 ''n'' 이하의 소수의 개수 \pi(n)을 추정하는 공식을 유도할 수 있다.

구체적으로, ''z'' 이하의 모든 소수의 곱을 P(z)라고 할 때, ''n'' 이하의 정수 중에서 \sqrt{n} 이하의 소수들의 배수를 제외하고 남은 수의 개수, 즉 \sqrt{n}보다 크고 ''n'' 이하인 소수의 개수는 \pi(n)-\pi \left(\sqrt{n}\, \right)이며, 이는 포함-배제의 원리에 따라 다음과 같이 표현될 수 있다:

:\pi(n)-\pi \left(\sqrt{n}\, \right)+1 = \sum_{d\mid P(\sqrt{n})} \! \mu(d)\left[\frac{\,n\,}{d}\right]

여기서 \mu(d)는 뫼비우스 함수이고, [x]는 ''x''를 넘지 않는 가장 큰 정수(바닥 함수)를 의미한다. (+1은 1을 세는 항이지만, 소수 개수 관점에서는 제외된다.)

더 일반적으로, 정수 집합 ''A''에서 ''z'' 이하의 소수의 배수를 모두 체로 걸러냈을 때 남는 원소의 개수 S(A,P)는 다음과 같이 표현할 수 있다. 이는 르장드르의 체라고도 불린다.

:S(A,P)=\sum_{d\mid P(z)}\!\mu(d) \left| A_{d\,} \right|

여기서 A_d는 ''A''의 원소 중 ''d''로 나누어 떨어지는 것들의 집합이다.

이러한 공식을 통해 ''n'' 이하 소수의 개수 \pi(n)에 대한 상한을 추정할 수 있으며 (z\leq\sqrt{n} 일 때 \pi(n) - \pi(z) + 1 \leq \sum_{d\mid P(z)} \! \mu(d)\left[\frac{\,n\,}{d}\right] 이용), 결과적으로 수가 커짐에 따라 소수의 밀도가 점차 감소하여 \lim_{x\to \infin}\! \frac{\,\pi (x)\,}{x} = 0임을 보일 수 있다.

그러나 포함-배제의 원리에만 의존하는 방식은 오차항이 상당히 커서(2^{\pi (z)} 항 등) 정밀한 추정에는 한계가 있다. 이러한 어려움을 극복하고 더 정확하게 소수의 분포를 연구하기 위해 현대적인 체 방법이 개발되었다. 이 방법들은 쌍둥이 소수 추측과 같은 수론의 중요한 미해결 문제들을 다루는 데 핵심적인 도구로 사용되고 있다.

3. 2. 계산 복잡도

에라토스테네스의 체는 컴퓨터 과학에서 알고리즘의 성능을 벤치마킹하는 데 사용되기도 한다.[14]

램(RAM) 모델에서 배열 업데이트가 O(1) 연산이라고 가정할 때, 기본 알고리즘의 시간 복잡도O(n \log \log n)이다. 이는 n까지의 소수의 역수의 합, 즉 소수 조화 급수가 점근적으로 \log \log n에 가까워진다는 사실에 기반한다. 기본 알고리즘의 공간 복잡도O(n)이다. 즉, n까지의 모든 수를 저장할 배열 공간이 필요하다.

입력값 n 자체의 크기가 아닌, 입력 길이에 대해서는 지수 시간 복잡도를 가지므로 유사 다항 시간 알고리즘으로 분류된다. 알고리즘의 비트 복잡도는 O(n (\log n) (\log \log n)) 비트 연산이며, 메모리 요구 사항은 O(n) 비트이다.[15]

실제 구현에서는 메모리 사용량을 줄이기 위해 페이지 분할(segmented sieve) 기법을 사용하기도 한다. 이 경우 시간 복잡도는 O(n \log \log n)으로 동일하게 유지하면서, 공간 요구 사항을 O\left(\frac{\sqrt{n}}{\log n}\right) 수준으로 줄일 수 있다. 이는 범위의 제곱근보다 작은 기본 소수들과 현재 처리 중인 세그먼트 페이지만 저장하면 되기 때문이다.

더 최적화된 특수한 분할 버전은 시간 복잡도를 O(n)까지 개선하고, 메모리 사용량은 O\left(\sqrt{n}\frac{\log \log n}{\log n}\right) 비트 수준으로 줄일 수 있다.[16][17][18]

빅 오 표기법은 점근적 성능을 나타내므로, 실제 실행 시간에서는 상수 인자나 낮은 차수의 항들이 영향을 미칠 수 있다는 점에 유의해야 한다. 예를 들어, 휠 인수분해를 적용한 프리처드 휠 체(Pritchard wheel sieve)와 같은 변형 알고리즘은 이론적으로 O(n)의 시간 복잡도를 달성할 수 있다.[16][17][18] 하지만 기본 구현은 많은 메모리를 요구하거나, 페이지 분할 시 기본 에라토스테네스의 체보다 메모리를 더 많이 사용하며(약 O\left(\frac{n}{\log n}\right) 비트), 실제 특정 범위 내에서는 최적화된 기본 에라토스테네스의 체보다 반드시 빠르지는 않을 수 있다.

3. 3. 주요 프로그래밍 언어

'''C++'''로 이 알고리즘을 다음과 같이 구현할 수 있다.



void Eratos(int n)

{

/* 만일 n이 1보다 작거나 같으면 함수 종료 */

if (n <= 1) return;

/* 2부터 n까지 n-1개를 저장할 수 있는 배열 할당

배열 참조 번호와 소수와 일치하도록 배열의 크기는

n+1 길이만큼 할당(인덱스 번호 0과 1은 사용하지 않음) */

bool* PrimeArray = new bool[n + 1];

/* 배열초기화: 처음엔 모두 소수로 보고 true값을 줌 */

for (int i = 2; i <= n; i++)

PrimeArray[i] = true;

/* 에라토스테네스의 체에 맞게 소수를 구함

만일, PrimeArray[i]가 true이면 i 이후의 i 배수는 약수로 i를

가지고 있는 것이 되므로 i 이후의 i 배수에 대해 false값을 준다.

PrimeArray[i]가 false이면 i는 이미 소수가 아니므로 i의 배수 역시

소수가 아니게 된다. 그러므로 검사할 필요도 없다.

또한 i*k (k < i) 까지는 이미 검사되었으므로 j시작 값은 i*2에서 i*i로 개선할 수 있다.

  • /

for (int i = 2; i * i <= n; i++)

{

if (PrimeArray[i])

{

for (int j = i * i; j <= n; j += i)

PrimeArray[j] = false;

}

}

// 이후의 작업 ...

}



'''Java'''로 구현



public class Eratos {

public static void main(String[] args) {

// ArrayList로 구현

ArrayList primeList;

// 사용자로부터의 콘솔 입력

Scanner scan = new Scanner(System.in);

int n = scan.nextInt();

// n <= 1 일 때 종료

if(n <= 1) return;

// n+1만큼 할당

primeList = new ArrayList(n+1);

// 0번째와 1번째를 소수 아님으로 처리

primeList.add(false);

primeList.add(false);

// 2~ n까지 소수로 설정

for(int i=2; i<=n; i++ )

primeList.add(i, true);

// 2부터 ~ i*i <= n

// 각각의 배수들을 지워간다.

for(int i=2; (i*i)<=n; i++){

if(primeList.get(i)){

for(int j = i*i; j<=n; j+=i) primeList.set(j, false);

//i*i 미만은 이미 처리되었으므로 j의 시작값은 i*i로 최적화할 수 있다.

}

}

StringBuffer sb = new StringBuffer();

sb.append("{");

for(int i=0; i<=n; i++){

if(primeList.get(i) == true){

sb.append(i);

sb.append(",");

}

}

sb.setCharAt(sb.length()-1, '}');

System.out.println(sb.toString());

}

}



'''Python'''(3.6.4)으로 구현[20]



def prime_list(n):

# 에라토스테네스의 체 초기화: n개 요소에 True 설정(소수로 간주)

sieve = [True] * n

# n의 최대 약수가 sqrt(n) 이하이므로 i=sqrt(n)까지 검사

m = int(n ** 0.5)

for i in range(2, m + 1):

if sieve[i] == True: # i가 소수인 경우

for j in range(i+i, n, i): # i이후 i의 배수들을 False 판정

sieve[j] = False

# 소수 목록 산출

return [i for i in range(2, n) if sieve[i] == True]

결과:



prime_list(20)

[2, 3, 5, 7, 11, 13, 17, 19]

max(prime_list(1000000))

999983



'''Haskell'''로 구현

타입 정보 있는 버전 (권장)



primes :: [Int]

primes = sieve [2..]

where sieve :: [Int] -> [Int]

sieve (prime : xs) = prime : sieve [x | x <- xs, x `mod` prime /= 0]

타입 정보 없는 버전 (Haskell의 타입 추론 이용)



primes = sieve [2..]

where sieve (prime : xs) = prime : sieve [x | x <- xs, x `mod` prime /= 0]

결과:


  • - 처음 소수 10개 찾기

take 10 primes

[2,3,5,7,11,13,17,19,23,29]

  • - 100보다 큰 소수 5개 찾기

take 5 $ filter (>100) primes

[101,103,107,109,113]


3. 4. 추가 프로그래밍 언어

=== C++ ===

C++를 이용한 에라토스테네스의 체 구현 예시는 다음과 같다. 배열을 사용하여 소수 여부를 표시하고, 반복문을 통해 배수를 제거하는 방식으로 동작한다.



void Eratos(int n)

{

/* 만일 n이 1보다 작거나 같으면 함수 종료 */

if (n <= 1) return;

/* 2부터 n까지 n-1개를 저장할 수 있는 배열 할당

배열 참조 번호와 소수와 일치하도록 배열의 크기는

n+1 길이만큼 할당(인덱스 번호 0과 1은 사용하지 않음) */

bool* PrimeArray = new bool[n + 1];

/* 배열초기화: 처음엔 모두 소수로 보고 true값을 줌 */

for (int i = 2; i <= n; i++)

PrimeArray[i] = true;

/* 에라토스테네스의 체에 맞게 소수를 구함

만일, PrimeArray[i]가 true이면 i 이후의 i 배수는 약수로 i를

가지고 있는 것이 되므로 i 이후의 i 배수에 대해 false값을 준다.

PrimeArray[i]가 false이면 i는 이미 소수가 아니므로 i의 배수 역시

소수가 아니게 된다. 그러므로 검사할 필요도 없다.

또한 i*k (k < i) 까지는 이미 검사되었으므로 j시작 값은 i*2에서 i*i로 개선할 수 있다.

  • /

for (int i = 2; i * i <= n; i++)

{

if (PrimeArray[i])

{

for (int j = i * i; j <= n; j += i)

PrimeArray[j] = false;

}

}

// 이후의 작업 ...

}



=== 자바 ===

자바에서는 ArrayList를 사용하여 에라토스테네스의 체를 구현할 수 있다.



import java.util.ArrayList;

import java.util.Scanner;

public class Eratos {

public static void main(String[] args) {

// ArrayList로 구현

ArrayList primeList;

// 사용자로부터의 콘솔 입력

Scanner scan = new Scanner(System.in);

int n = scan.nextInt();

// n <= 1 일 때 종료

if(n <= 1) return;

// n+1만큼 할당

primeList = new ArrayList(n+1);

// 0번째와 1번째를 소수 아님으로 처리

primeList.add(false);

primeList.add(false);

// 2~ n까지 소수로 설정

for(int i=2; i<=n; i++ )

primeList.add(i, true);

// 2부터 ~ i*i <= n

// 각각의 배수들을 지워간다.

for(int i=2; (i*i)<=n; i++){

if(primeList.get(i)){

for(int j = i*i; j<=n; j+=i) primeList.set(j, false);

//i*i 미만은 이미 처리되었으므로 j의 시작값은 i*i로 최적화할 수 있다.

}

}

StringBuffer sb = new StringBuffer();

sb.append("{");

for(int i=0; i<=n; i++){

if(primeList.get(i) == true){

sb.append(i);

sb.append(",");

}

}

sb.setCharAt(sb.length()-1, '}');

System.out.println(sb.toString());

}

}



=== 파이썬 ===

파이썬(3.6.4 버전 기준)을 사용한 구현 예시는 다음과 같다.[20] 리스트불리언 값을 활용하여 비교적 간결하게 작성할 수 있다.



def prime_list(n):

# 에라토스테네스의 체 초기화: n개 요소에 True 설정(소수로 간주)

sieve = [True] * n

# n의 최대 약수가 sqrt(n) 이하이므로 i=sqrt(n)까지 검사

m = int(n ** 0.5)

for i in range(2, m + 1):

if sieve[i] == True: # i가 소수인 경우

for j in range(i+i, n, i): # i이후 i의 배수들을 False 판정

sieve[j] = False

# 소수 목록 산출

return [i for i in range(2, n) if sieve[i] == True]



실행 결과 예시는 다음과 같다.



prime_list(20)

[2, 3, 5, 7, 11, 13, 17, 19]

max(prime_list(1000000))

999983



=== 하스켈 ===

함수형 프로그래밍 언어인 하스켈로도 에라토스테네스의 체를 구현할 수 있다. 다음은 타입 정보가 명시된 버전과 타입 추론을 이용한 버전의 예시이다.
타입 정보 명시 버전 (권장)

primes :: [Int]

primes = sieve [2..]

where sieve :: [Int] -> [Int]

sieve (prime : xs) = prime : sieve [x | x <- xs, x `mod` prime /= 0]


타입 정보 생략 버전 (타입 추론 이용)

primes = sieve [2..]

where sieve (prime : xs) = prime : sieve [x | x <- xs, x `mod` prime /= 0]



실행 결과 예시는 다음과 같다.


  • - 처음 소수 10개 찾기

take 10 primes

[2,3,5,7,11,13,17,19,23,29]

  • - 100보다 큰 소수 5개 찾기

take 5 $ filter (>100) primes

[101,103,107,109,113]


4. 변형 및 응용

에라토스테네스의 체는 기본적인 형태 외에도 다양한 방식으로 개선되거나 응용될 수 있다. 기본적인 개선 방법으로는 소수 ''p''의 배수를 제거할 때, ''p''의 제곱(p^2)부터 시작하는 것이 있다. 이는 ''p''보다 작은 소수의 배수들은 이미 그 이전에 제거되었기 때문에 효율적이다.[1] 또한, 처음부터 짝수를 제외하고 홀수만을 대상으로 하거나, 더 나아가 휠 인수분해 기법을 적용하여 초기 목록에서 2뿐만 아니라 3, 5 등 작은 소수들의 배수까지 미리 제거함으로써 계산량을 줄일 수도 있다.[1][3][6]

에라토스테네스의 체 원리는 단순히 소수를 찾는 것을 넘어, 특정 수 이하의 소수 개수를 추정하는 데에도 응용된다. 르장드르는 포함-배제의 원리와 뫼비우스 함수 \mu(d)를 이용하여 주어진 수 ''n'' 이하의 소수 개수(\pi(n))를 구하는 공식을 만들었다. 이는 '르장드르의 체'라고도 불리며, 특정 수 ''z'' 이하의 모든 소수의 곱을 P(z)라고 할 때, 집합 ''A''에서 ''z'' 이하 소수들의 배수를 제외하고 남은 원소의 개수 S(A,P)는 다음과 같은 형태로 표현될 수 있다.[5]

:S(A,P)=\sum_{d\mid P(z)}\!\mu(d) \left| A_{d\,} \right|

여기서 A_d는 ''A''의 원소 중 ''d''로 나누어 떨어지는 것들의 집합을 의미한다. 이 공식을 이용하면 \pi(n)의 상한을 추정할 수 있고, \lim_{x\to \infin}\! \frac{\,\pi (x)\,}{x} = 0 와 같은 결과를 증명할 수 있다.[5]

하지만 르장드르의 체와 같이 포함-배제의 원리에만 의존하는 방식은 오차항이 커지는 단점이 있다. 이러한 한계를 극복하고 더 정교하게 집합의 크기를 추정하기 위해 현대적인 체 방법(sieve methods)이 개발되었다. 이 방법들은 쌍둥이 소수 추측과 같은 어려운 수론 문제들을 연구하는 데 중요한 도구로 사용되고 있다.[5]

4. 1. 분할 체

에라토스테네스의 체는 소수를 찾는 간단하고 효과적인 방법이지만, 처리해야 하는 수의 범위 ''n''이 매우 커지면 메모리 사용량이 많아진다는 단점이 있다. 특히 큰 ''n''에 대해서는 전체 수 목록을 메모리에 저장하기 어려울 수 있으며, 중간 정도의 ''n''이라도 캐시 활용 효율이 떨어져 속도가 느려질 수 있다. 알고리즘이 전체 배열을 순차적으로 접근해야 하므로 참조 지역성이 낮기 때문이다.

이러한 메모리 문제를 해결하기 위해 분할 체(segmented sieve) 방식이 사용된다. 분할 체는 전체 범위를 한 번에 처리하는 대신, 작은 구간(세그먼트)으로 나누어 단계적으로 체질을 수행한다.[9] 이 방식은 1970년대부터 알려졌으며, 작동 원리는 다음과 같다.[10]

1. 전체 범위인 2부터 ''n''까지를 일정한 크기 Δ (Δ ≤ √''n'')의 여러 세그먼트로 나눈다.

2. 첫 번째 세그먼트(가장 작은 수들을 포함하는 구간)에 대해서는 기본적인 에라토스테네스의 체 알고리즘을 사용하여 소수를 찾는다.

3. 이후 각 세그먼트에 대해 순서대로 다음 작업을 반복한다. 현재 처리 중인 세그먼트의 최댓값을 ''m''이라고 하자.

  • 크기가 Δ인 부울 배열(각 수가 소수인지 아닌지를 표시하는 배열)을 준비한다.
  • 지금까지 찾은 소수들 중 ''p'' ≤ √''m'' 인 각 소수 ''p''에 대해, 현재 세그먼트(''m'' - Δ 와 ''m'' 사이) 내에 있는 ''p''의 배수들을 찾아 배열에 비소수(합성수)로 표시한다. 배수는 현재 세그먼트 내에서 ''p''의 가장 낮은 배수부터 시작하여 ''p'' 단계로 열거한다.
  • 배수 표시가 끝나면, 배열에서 비소수로 표시되지 않은 위치에 해당하는 수들이 현재 세그먼트의 소수이다. 이 단계에서 새로 발견된 소수들의 배수는 고려할 필요가 없다. 왜냐하면 ''k'' ≥ 1에 대해 (''k''Δ + 1)2 > (''k''+1)Δ 이므로 배수를 표시할 필요가 없기 때문이다.


분할 체에서 세그먼트 크기 Δ를 약 √''n''으로 선택하면, 필요한 메모리 공간은 O(√''n'') 수준으로 줄어든다. 시간 복잡도는 기본적인 에라토스테네스의 체와 동일하게 유지되면서 메모리 효율성을 크게 높일 수 있다.

만약 √''n'' 미만의 소수 목록조차 메모리에 저장하기 어려울 정도로 상한 ''n''이 극단적으로 큰 경우에는, 조나단 P. 소렌슨(Jonathan P. Sorenson)이 개발한 '의사 제곱 소수 체'(pseudosquares prime sieve)와 같이 시간은 더 걸리지만 메모리를 훨씬 적게 사용하는 다른 체 알고리즘을 사용할 수 있다.[11]

4. 2. 증분 체

에라토스테네스의 체의 증분 방식[12]소수의 배수 생성과 소수 생성을 번갈아 가며 진행하여, 이론적으로 무한히 많은 소수를 생성할 수 있다(즉, 상한 없이). 이 방식에서는 각 소수 ''p''의 배수를 ''p''의 제곱(p^2)부터 시작하여 ''p''씩 증가시키며 직접 생성한다. 홀수 소수의 경우에는 2p씩 증가시킨다. 효율성을 해치지 않으려면, 각 소수의 제곱에 도달했을 때 배수 생성을 시작하는 것이 좋다.

이러한 증분 방식은 데이터 흐름 프로그래밍 패러다임에서 다음과 같이 기호적으로 표현할 수 있다.

`''primes'' = [''2'', ''3'', ...] \

여기서 `\` 기호는 숫자들의 상대 여집합을 나타내며, 리스트 컴프리헨션 표기법을 사용하여 등차수열 형태의 배수 목록을 생성하고 이를 기존 소수 후보 목록에서 제외하는 과정을 보여준다.

소수를 생성하는 다른 방법으로는 나눗셈 시도가 있다. 이 방법은 각 숫자를 이전에 찾은 소수들로 나누어 보아 나누어 떨어지지 않으면 소수로 판정하는 방식으로, 합성수를 반복적으로 걸러낸다. 이는 에라토스테네스의 체와는 다른 방식이지만 종종 혼동되곤 한다.[12] 에라토스테네스의 체는 소수로 직접 나누어 테스트하는 대신 합성수를 직접 생성하여 제거하는 방식이다. 이론적으로 나눗셈 시도는 소수 범위를 생성하는 데 있어 에라토스테네스의 체보다 복잡성 측면에서 효율이 떨어진다.[12]

각 숫자가 소수인지 판별할 때, 최적화된 나눗셈 시도 알고리즘은 해당 숫자의 제곱근을 넘지 않는 모든 소수들로 나누어 본다. 반면, 에라토스테네스의 체는 각 합성수를 그 소인수로부터 직접 생성하며, 그 과정에서 합성수가 아닌 숫자들, 즉 소수를 자연스럽게 얻게 된다. 널리 알려진 1975년 데이비드 터너가 작성한 함수형 프로그래밍 체 코드는[13] 종종 에라토스테네스의 체의 예시로 제시되지만,[6] 실제로는 최적화되지 않은 나눗셈 시도 방식의 체이다.[12]

4. 3. 오일러의 체

오일러제타 함수 곱 공식 증명에는 에라토스테네스의 체의 한 변형이 포함되어 있는데, 이는 각 합성수정확히 한 번만 제거하는 특징을 가진다.[8] 이는 하나의 합성수가 여러 소수의 배수로 중복되어 제거될 수 있는 기존 에라토스테네스의 체와 구별되는 점이다. 이 방법은 나중에 Gries와 Misra (1978)에 의해 재발견되었으며, 선형 시간에 작동하는 효율적인 알고리즘으로 밝혀졌다.[19]

오일러의 체는 다음과 같은 방식으로 작동한다:

# 2부터 목표 상한선 ''n''까지의 정수 목록으로 시작한다.

# 목록의 가장 앞에 있는 수(처음에는 2)를 다음 소수 ''p''로 식별한다.

# ''p''를 현재 목록에 있는 모든 수(자기 자신 포함)와 곱한다.

# 이렇게 곱해서 얻은 결과(합성수)들을 목록에서 제거할 대상으로 표시한다.

# 목록의 가장 앞에 있던 소수 ''p''와 표시된 합성수들을 실제 목록에서 제거한다.

# 목록에 수가 남아 있는 한, 2단계부터 과정을 반복한다.

예를 들어, 홀수 목록 (3, 5, 7, 9, 11, 13, 15, ...)에서 시작하는 경우를 살펴보자 (이는 알고리즘의 첫 단계인 2의 배수 제거가 끝난 상태와 유사하다).

  • 단계 1 (p=3): 3을 소수로 식별한다. 목록의 모든 수(3, 5, 7, 9, 11, 13, 15, ...)에 3을 곱하여 (9, 15, 21, 27, 33, 39, 45, ...)를 얻고, 이들을 제거 대상으로 표시한다. 목록에서 3과 표시된 수들을 제거한다. 남은 목록은 (5, 7, 11, 13, 17, 19, 23, 25, ...)가 된다.
  • 단계 2 (p=5): 5를 소수로 식별한다. 남은 목록의 모든 수(5, 7, 11, 13, 17, 19, 23, 25, ...)에 5를 곱하여 (25, 35, 55, 65, 85, 95, 115, 125, ...)를 얻고 표시한다. 목록에서 5와 표시된 수들을 제거한다. 남은 목록은 (7, 11, 13, 17, 19, 23, 29, ...)가 된다.
  • 이 과정을 계속 반복한다.


''k''번째 단계가 완료되면, 목록에는 처음 ''k''개의 소수와 서로소인 수들만 남게 된다. 이 시점에서 목록의 가장 앞에 있는 수는 다음 소수가 되며, 이 소수의 제곱보다 작은 목록 안의 모든 수는 소수이다. 따라서 특정 상한선까지의 소수를 찾고자 할 때, 다음으로 식별된 소수가 목표 상한선의 제곱근을 초과하면, 그 시점에 목록에 남아 있는 모든 수는 소수라고 결론지을 수 있다.[8]

오일러의 체를 사용할 때 주의할 점은, 어떤 단계에서 제거될 예정인 합성수라도 그 단계에서 다른 합성수를 생성(표시)하는 데 사용될 수 있다는 것이다. 예를 들어, 3의 배수를 제거하는 단계에서 제거 대상인 9는 3 × 9 = 27을 생성하는 데 사용되고, 마찬가지로 제거 대상인 15는 3 × 15 = 45를 생성하는 데 사용된다. 따라서 알고리즘 구현 시 이러한 점을 고려해야 한다.[8]

참조

[1] 논문 "''{{lang|el|Κόσκινον Ερατοσθένους}}'' or, The Sieve of Eratosthenes. Being an account of his method of finding all the Prime Numbers," https://www.jstor.or[...] Philosophical Transactions 1772
[2] 서적 Nicomachi Geraseni Pythagorei Introductionis arithmeticae libri II, chapter XIII, 3 https://archive.org/[...] B.G. Teubner
[3] 서적 Introduction to Arithmetic; translated into English by Martin Luther D'Ooge; with studies in Greek arithmetic by Frank Egleston Robbins and Louis Charles Karpinski, chapter XIII, 3 The Macmillan Company
[4] 논문 "Extension of the Sieve of Eratosthenes to arithmetical progressions and applications" https://www.jstor.or[...] Annals of Mathematics, Second Series 1909
[5] 서적 Programming in Prolog
[6] 간행물 Functional Pearl: Lazy wheel sieves and spirals of primes http://eprints.white[...]
[7] 서적 Algorithms in C++ Addison-Wesley
[8] 문서 An Introduction to Prime Number Sieves http://research.cs.w[...] Department of Computer Sciences University of Wisconsin-Madison 1990-01-02
[9] 서적 Prime Numbers: A Computational Perspective Springer
[10] 간행물 The segmented sieve of Eratosthenes and primes in arithmetic progressions to 1012
[11] 논문 "The pseudosquares prime sieve" https://link.springe[...] Proceedings of the 7th International Symposium on Algorithmic Number Theory 2006
[12] 논문 "The Genuine Sieve of Eratosthenes" http://www.cs.hmc.ed[...] 2008-10-09
[13] 문서 SASL language manual Department of Computational Science, University of St. Andrews
[14] 뉴스 One Million Primes Through the Sieve https://archive.org/[...] BYTE 1985
[15] 논문 "Linear prime-number sieves: a family tree," Sci. Comput. Programming 1987
[16] 간행물 "A sublinear additive sieve for finding prime numbers" 1981
[17] 간행물 Explaining the wheel sieve 1982
[18] 간행물 "Fast compact prime number sieves" (among others) 1983
[19] 간행물 A linear sieve algorithm for finding prime numbers https://ecommons.cor[...] 1978-12
[20] 웹인용 Project Euler Problem 10 {{!}} Jason's Code Blog http://code.jasonbhi[...] 2018-03-13



본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.

문의하기 : help@durumis.com