에라토스테네스의 체
N 이하의 소수를 한 번에 구하는 알고리즘으로, 소수의 배수를 반복적으로 지워 나가는 방식입니다.
핵심 아이디어
소수 p를 찾으면 p의 배수는 모두 합성수이므로 제거합니다.
[ 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 제거
2 3 5 7 9 11 13 15 17 19 21 23 25 27 29
p=3: 9 15 21 27 제거 (6 12 18 24 30은 이미 제거됨)
2 3 5 7 11 13 17 19 23 25 29
p=5: 25 제거 (10 15 20 30은 이미 제거됨)
2 3 5 7 11 13 17 19 23 29
p=7: 7² = 49 > 30 → 중단
결과 소수: 2 3 5 7 11 13 17 19 23 29
구현
boolean[] sieve(int n) {
boolean[] isComposite = new boolean[n + 1]; // false = 소수
isComposite[0] = isComposite[1] = true;
for (int i = 2; (long) i * i <= n; i++) {
if (!isComposite[i]) {
for (int j = i * i; j <= n; j += i) {
isComposite[j] = true;
}
}
}
return isComposite;
}
핵심 최적화 2가지
① 외부 루프를 √N까지만
N의 합성수는 반드시 √N 이하의 소인수를 가집니다.
→ i > √N이면 i의 배수는 이미 더 작은 소수에 의해 제거됨
→ i * i <= n 조건으로 중단
② 내부 루프를 i²에서 시작
p = 5일 때:
5 × 2 = 10 → p=2 단계에서 이미 제거
5 × 3 = 15 → p=3 단계에서 이미 제거
5 × 4 = 20 → p=2 단계에서 이미 제거
5 × 5 = 25 → ← 여기서부터 새로 제거 필요
→ j = i * i 에서 시작하면 중복 작업 없음
i * i계산 시 int 범위 초과 가능 —(long) i * i <= n으로 캐스팅 필수
시간복잡도
| 단계 | 복잡도 |
|---|---|
| 전체 | O(n log log n) |
| 단순 소수 판별 (1개) | O(√n) |
| n개 소수 판별 (체 없이 반복) | O(n√n) |
체의 작업량 = n/2 + n/3 + n/5 + n/7 + ...
≈ n × Σ(1/p) (소수 p에 대한 합)
≈ n × log log n (소수 역수 합의 점근식)
n = 10⁶: 약 2천만 연산 / n = 10⁷: 약 2.3천만 연산 — 실용적으로 거의 선형
소수 목록 추출
// 체 결과에서 소수 리스트 만들기
List<Integer> primes = new ArrayList<>();
for (int i = 2; i <= n; i++) {
if (!isComposite[i]) primes.add(i);
}
구간 소수 (Segmented Sieve)
N이 매우 크지만 [L, R] 범위만 필요할 때. 전체 배열을 만들기 어려운 경우 사용.
// 아이디어: √R 이하의 소수로 [L, R] 범위만 체질
boolean[] segSieve(long L, long R) {
int sqrtR = (int) Math.sqrt(R);
boolean[] smallComp = sieve(sqrtR); // √R 이하 소수 구하기
boolean[] isComp = new boolean[(int)(R - L + 1)]; // [L, R] 범위
for (int p = 2; p <= sqrtR; p++) {
if (!smallComp[p]) {
long start = Math.max((long) p * p, ((L + p - 1) / p) * p);
for (long j = start; j <= R; j += p) {
isComp[(int)(j - L)] = true;
}
}
}
if (L == 1) isComp[0] = true; // 1은 소수 아님
return isComp;
}
단일 소수 판별 vs 에라토스테네스의 체
| 상황 | 선택 | 이유 |
|---|---|---|
| 소수 1개 판별 | 단일 판별 O(√n) | 체 초기화 비용이 더 큼 |
| N 이하 소수 전부 | 에라토스테네스의 체 | O(n log log n) |
| 소수 판별을 여러 번 | 에라토스테네스의 체 미리 계산 | 이후 조회 O(1) |
| 매우 큰 N, 좁은 구간 | 구간 체 | 메모리 절약 |
단일 소수 판별
boolean isPrime(int n) {
if (n < 2) return false;
for (int i = 2; (long) i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}
주의사항
배열 크기
// N = 10^6이면
boolean[] isComposite = new boolean[1_000_001]; // N+1 크기 필요
// 자주 하는 실수: N 선언 후 N+1 크기 잊기
int N = 1_000_000;
boolean[] arr = new boolean[N]; // X arr[N] 접근 시 IndexOutOfBounds
boolean[] arr = new boolean[N+1]; // O
1과 0 처리
isComposite[0] = isComposite[1] = true; // 0, 1은 소수 아님
i * i 오버플로
// i = 50,000, i*i = 2,500,000,000 → int 최대값(2,147,483,647) 초과
for (int i = 2; i * i <= n; i++) // X 오버플로 가능
for (int i = 2; (long) i * i <= n; i++) // O
핵심 정리
-
N 이하 소수를 O(n log log n)에 일괄 구하는 알고리즘 — 단일 판별 O(√n)보다 여러 번 필요할 때 압도적으로 유리
-
핵심 최적화: 외부 루프 √N까지, 내부 루프 i²부터 시작
-
(long) i * i <= n캐스팅 필수 — int 오버플로 방지 -
N이 커서 메모리 부족 시 → 구간 체(Segmented Sieve)로 [L, R] 범위만 처리
-
소수 조회가 한 번뿐이라면 단일 판별 O(√n)이 더 빠름