핵심 인사이트
- 에라토스테네스의 체(Sieve of Eratosthenes)는 2~N까지 소수를 한 번에 구하는 O(N log log N) 알고리즘 — "소수의 배수를 모두 제거"하는 원리로, N이 클 때 개별 소수 판별을 N번 반복하는 O(N√N)보다 압도적으로 효율적이다.
- 배수 제거 시작점이 p² — 소수 p의 배수를 제거할 때 2p, 3p, ...(p-1)p는 이미 앞선 소수에 의해 제거됐으므로, p²부터 시작하면 불필요한 연산을 줄일 수 있다.
- 비트 배열과 분할 체로 메모리와 캐시 효율을 극대화 — N=10억 범위의 소수 구하기는 Segmented Sieve로 분할하여 L1 캐시 크기에 맞춰 처리해야 실용적이다.
Ⅰ. 알고리즘 원리
에라토스테네스의 체 동작 원리:
1단계: 2부터 N까지 배열 생성
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, ...]
2단계: 소수 p=2 → p²부터 p 배수 제거
제거: 4, 6, 8, 10, 12, ...
[2, 3, _, 5, _, 7, _, 9, _, 11, _, 13, ...]
3단계: 다음 소수 p=3 → p²=9부터 3 배수 제거
(6은 이미 제거, 9부터)
제거: 9, 12, 15, ...
4단계: p=5 → 25부터 제거
(10, 15, 20은 이미 제거)
5단계: p > √N이면 종료
(이후 소수의 배수는 이미 모두 제거됨)
예: N=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]
p=2: 4,6,8,10,12,14,16,18,20,22,24,26,28,30 제거
p=3: 9,15,21,27 제거 (6,12...는 이미 제거)
p=5: 25 제거 (10,15,20,30은 이미 제거)
p=7 > √30≈5.47이므로 종료
결과: [2,3,5,7,11,13,17,19,23,29]
왜 p²부터 시작?
소수 p를 처리할 때:
2p, 3p, ..., (p-1)p는 모두 2, 3, ..., p-1 중 소수의 배수
→ 이미 이전 단계에서 제거됨
→ p²부터 처리하면 됨
📢 섹션 요약 비유: 에라토스테네스의 체 = 번호표 소각 — 2번 배수(짝수) 소각, 3번 배수 소각, 5번 배수 소각... 남은 번호표가 모두 소수! 불필요한 것을 제거하는 소수 대청소!
Ⅱ. 구현
# 기본 구현 O(N log log N)
def sieve_of_eratosthenes(n):
is_prime = [True] * (n + 1)
is_prime[0] = is_prime[1] = False # 0, 1은 소수 아님
p = 2
while p * p <= n: # p <= √n
if is_prime[p]:
# p² 부터 p 배수 제거
for multiple in range(p * p, n + 1, p):
is_prime[multiple] = False
p += 1
return [i for i in range(2, n + 1) if is_prime[i]]
# 테스트
primes = sieve_of_eratosthenes(30)
print(primes) # [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
primes_100 = sieve_of_eratosthenes(100)
print(len(primes_100)) # 25 (100 이하 소수 25개)
# 성능 비교
# N = 1,000,000
# 개별 판별 O(N√N): 약 10^9 연산
# 에라토스테네스 O(N log log N): 약 3.8×10^6 연산
# → 263배 빠름
# 비트 배열 최적화 (메모리 8배 절감)
def sieve_bitarray(n):
from bitarray import bitarray
is_prime = bitarray(n + 1)
is_prime.setall(True)
is_prime[0] = is_prime[1] = False
p = 2
while p * p <= n:
if is_prime[p]:
is_prime[p*p::p] = False # 슬라이스 연산
p += 1
return [i for i in range(2, n+1) if is_prime[i]]
메모리 사용량:
N = 10,000,000 (천만)
bool 배열: 10MB
비트 배열: 1.25MB (8배 절감)
N = 1,000,000,000 (10억)
bool 배열: 1GB (불가)
비트 배열: 125MB (가능)
Segmented Sieve: L1 캐시 크기(32KB)로 분할
📢 섹션 요약 비유: 체 구현 — is_prime 배열에서 배수 제거. N=100만에서 개별 판별보다 263배 빠름. 비트 배열로 메모리도 8배 절감!
Ⅲ. 분할 체 (Segmented Sieve)
Segmented Sieve:
대용량 N(10억~)을 작은 세그먼트로 분할
L1 캐시(32KB)에 맞춰 처리
알고리즘:
1. √N까지의 소수를 기본 체로 구함
2. N을 세그먼트 크기(seg_size)로 분할
3. 각 세그먼트에 기본 소수의 배수 표시
세그먼트 크기 = min(√N, 캐시 크기에 맞는 크기)
```python
import math
def segmented_sieve(n):
limit = int(math.sqrt(n)) + 1
# 기본 소수 (√N까지)
base_primes = sieve_of_eratosthenes(limit)
primes = list(base_primes)
# 세그먼트 처리
seg_size = limit
low = limit + 1
while low <= n:
high = min(low + seg_size - 1, n)
sieve = [True] * (high - low + 1)
for p in base_primes:
# 세그먼트 내 p의 첫 배수 위치
start = ((low + p - 1) // p) * p
if start == p:
start += p # p 자신은 제외
for j in range(start, high + 1, p):
sieve[j - low] = False
for i in range(len(sieve)):
if sieve[i]:
primes.append(low + i)
low = high + 1
return primes
참고로 10억 이하의 소수 개수는 50,847,534개이며, 이는 소수 정리 (Prime Number Theorem)의 근사값 n / ln(n)와 비슷한 수준이다.
장점: 캐시 친화적 (세그먼트가 L1 캐시에 들어감) 메모리 효율적 (전체 배열 불필요)
성능: N = 10억: 기본 체: 1GB 메모리 필요 분할 체: 32KB × 반복 = 32KB만 필요 속도: 3~5배 빠름 (캐시 효율)
> 📢 **섹션 요약 비유**: 분할 체 = 큰 교실을 작은 그룹으로 — 1000명 전체(10억) 한 번에 처리 불가. 30명씩 그룹(세그먼트)으로 나눠서 체 처리. 책상(캐시)에 다 올라오는 크기로!
---
## Ⅳ. 소수 정리와 응용
소수 정리 (Prime Number Theorem): N 이하 소수 개수 π(N) ≈ N / ln(N)
π(100) = 25 (실제) vs 100/ln(100) ≈ 21.7 π(1,000,000) = 78,498 vs 10^6/ln(10^6) ≈ 72,382 N이 클수록 근사 정확도 향상
소수 관련 개념: 쌍둥이 소수 (Twin Primes): (3,5), (5,7), (11,13), (17,19), ... 차이 = 2인 소수 쌍, 무한히 많은지 미증명 (추측)
메르센 소수: 2^n - 1 형태 소수 M(2)=3, M(3)=7, M(5)=31, M(7)=127 현재 알려진 최대 소수: 2^82,589,933 - 1 (2018)
응용:
-
암호학: RSA: 큰 소수 p, q 생성 (512~4096 비트) 에라토스테네스 체로 소수 후보 필터 후 밀러-라빈 테스트로 확인
-
해시 테이블: 크기를 소수로 설정 → 해시 충돌 감소
-
코딩테스트: "N까지 소수의 합" → 에라토스테네스 O(N log log N) "N이 소수인지" → 밀러-라빈 O(k log²N) "소인수분해" → 폴라드 로 알고리즘
-
정수론 문제: GCD + 소수 = 정수론 알고리즘의 핵심
> 📢 **섹션 요약 비유**: 소수 응용 — RSA 암호는 큰 소수(비밀번호), 해시 테이블은 소수 크기(충돌 방지), 코딩테스트는 체 알고리즘(빠른 계산). 수학이 현대 컴퓨팅을 지탱!
---
## Ⅴ. 실무 시나리오 — 암호화 소수 생성
시나리오: RSA 키 생성에서 소수 판별
목표: 512비트 소수 p, q 생성 (RSA-1024) (10^154 범위의 소수)
과정:
-
에라토스테네스 체로 작은 소수 목록 생성 (100,000 이하) 이유: 큰 난수가 작은 소수로 나뉘는지 빠르게 검사
-
난수 n (512비트) 생성 홀수로 만들기 (짝수는 소수 불가: n | 1)
-
작은 소수 테스트: for p in small_primes: if n % p == 0: 버리고 다음 난수
속도: n이 합성수일 확률 ~70% 빠르게 걸러냄
-
밀러-라빈 확률 소수 테스트: k=15 라운드 → 오류 확률 < 4^(-15) ≈ 10^(-9)
-
통과하면 소수로 채택
통계: 512비트 소수 밀도: 약 1/ln(2^512) ≈ 1/355 평균 355번 난수 생성하면 소수 1개
에라토스테네스의 역할: 1단계 빠른 필터 역할 100,000 이하 소수: 9,592개 (30밀리초 생성) 큰 합성수 ~70% 제거 → 밀러-라빈 부담 감소
성능: 에라토스테네스 없이: 355번 × 밀러-라빈 시간 에라토스테네스 포함: 355번 × (빠른 체크 + 0.3 × 밀러-라빈) → 3배 빠름
> 📢 **섹션 요약 비유**: 소수 생성 파이프라인 — 에라토스테네스(초등 검문소: 70% 걸러냄) + 밀러-라빈(전문 검사: 나머지 확인). 두 단계 협력으로 RSA 소수 효율적 생성!
---
## 📌 관련 개념 맵
에라토스테네스의 체 +-- 핵심 원리: 배수 제거 +-- 최적화 | +-- p²부터 시작 | +-- 비트 배열 | +-- Segmented Sieve +-- 복잡도: O(N log log N) +-- 응용 | +-- RSA 소수 생성 | +-- 해시 테이블 크기 | +-- 코딩테스트 소수 문제 +-- 관련 개념 +-- 밀러-라빈 소수 테스트 +-- 소수 정리 (π(N) ≈ N/ln N)
---
## 📈 관련 키워드 및 발전 흐름도
[에라토스테네스 (기원전 240년)] 알렉산드리아 도서관장 손으로 계산한 소수 체 | v [소수 정리 (1896)] 아다마르, 발레 푸생 π(N) ≈ N/ln(N) 증명 | v [밀러-라빈 테스트 (1980)] 확률 소수 판별 대용량 소수 생성 | v [Segmented Sieve (1987)] 캐시 최적화 10억 이상 범위 | v [현재: 양자 시대 위협] 쇼어 알고리즘: 소수 기반 RSA 해독 Post-Quantum 암호로 전환 중
---
## 👶 어린이를 위한 3줄 비유 설명
1. 에라토스테네스의 체 = 번호표 소각 — 2번 배수 다 소각, 3번 배수 소각... 남은 번호표가 소수! 배수를 지우는 대청소!
2. p²부터 시작 = 이미 한 청소 반복 안 하기 — 3의 배수 중 6은 이미 2가 청소했어요. 9부터(3²) 시작하면 더 효율적!
3. Segmented Sieve = 큰 책 쪽 나눠 읽기 — 1000페이지 책을 30페이지씩 나눠 읽으면 책상(캐시)에 올라가요. 10억도 빠르게 처리!