어떤 자연수 N이 소수인지 판별하는 방법에는 여러가지 방법이 있다.
소수는 1 또는 자기 자신으로만 나누어지기 때문에 위의 방법으로 나누어 떨어지지 않는다면 그 수는 소수라고 할 수 있다.
만약, 이 어떤 두 수로 나누어진다고 한다면, 의 형태가 되고, p와 q 둘 중 하나는 반드시 의 값 이하일 것이다.
따라서 까지만 나눠도 소수인지 판별 가능하다.
이 방법의 단점:
시간 복잡도가 이기 때문에 N이 커지면 소수를 판별하는데 시간이 오래 걸린다.N이하의 소수를 모두 구하라고 한다면 )의 시간 복잡도를 갖기 때문에 의 범위가 매우 제한적이다.
어떠한 자연수 N이 소수인지를 판별하는 방법 중 고대 그리스 수학자 에라토스테네스가 발견한 에라토스테네스의 체가 가장 보편적으로 이용된다.
에라토스테네스의 체는 초기값이 0인 N개의 원소를 갖는 배열(A)를 선언하여, i=2부터 i=N까지 순서대로 돌면서 소수가 아닌 수를 체크해주는 방식으로 소수를 판별한다.
결국 i가 소수인 경우에만 A[i] = 0이 되고, 나머지는 모두 1이 된다.
소수의 배수들은 모두 합성수라는 성질을 이용하여 체로 걸러내듯이 합성수들을 모두 걸러내는 방법이다.
시간 복잡도는 으로, 보통 100만까지 정도의 소수를 구하는 문제가 출제된다.
#include <stdio.h>
int main() {
int arr[1001] = {0};
for(int i=2; i<=1000; i++) { // 합성수 걸러내는 반복문
if(arr[i] == 0) {
for(int j=2; i*j<=1000; j++) {
arr[i*j] = 1; // 합성수 체크
}
}
}
for(int i=2; i<=1000; i++) { // 걸러낸 소수 출력
if(arr[i] == 0) {
printf("%d ", i);
}
}
return 0;
}
🖥 출력 결과:
에라토스테네스의 체 원리를 생각해보면, 현재 수가 아직 걸러지지 않았다면 그 수는 소수이고, 현재 수의 배수드를 제거해나가는 방식이다.
이때 제거되는 수는 합성수이고, 제일 처음 제거될 때 이용되는 소수가 k가 갖는 가장 작은 소인수이다.
int prime[1000001];
int main() {
int n = 1000000;
for(int i=1; i<=n; i++) { prime[i] = i; }
for(int i=2; i<=n; i++) {
if(prime[i] == i) { // 아직 걸러지지 않음
for(int j=2; i*j<=N; j++) { // i의 배수
if(prime[i*j] == i*j) { prime[i*j] = i; }
}
}
}
자연수 k를 소인수분해를 하는 가장 기초적인 방법은 직접 2부터 까지 나눠주는 방법이다.
만약, 현재 k가 i로 나누어떨어지면 i로 더이상 나누어떨어지지 않을 때까지 나눠준다. 나누어지는 횟수 만큼 소인수 i가 k에 포함되어있는 것이다.''
까지 모두 나누어주었을 때, 아직 k가 1이 아니라면, 이 또한 보다 큰 소수 이며, 기존의 k의 소인수가 된다.하지만 이 방식으로 2부터 n까지의 모든 자연수 k에 대해 소인수분해를 한다면 의 시간 복잡도를 가지게 된다.
1번에서 설명한 k가 갖는 가장 작은 소인수를 이용하면 만에 2 부터 n까지의 모든 자연수를 소인수분해할 수 있다.
각 2부터 n까지 모든 자연수의 가장 작은 소인수를 전처리 해두었다고 가정하자.
그러면 자연수 k에서 시작해서, 가장 작은 소인수를 나누어주는 과정을 반복하면 소인수분해가 완성된다.
여기서 가장 작은 소인수는 항상 2 이상이므로 안에 가능하다.
int prime[1000001];
vector<int> fac[1000001]; // cpp의 문법이다.
int main(void) {
int N = 1000000;
for(int i=1; i<=N; i++) { prime[i] = i; }
for(int i=2; i<=N; i++) { // 1번과 같은 코드
if(prime[i] == i) {
for(itn j=2; j*i<=N; j++) {
if(prime[i*j] == i*j) { prime[i*j] = i; }
}
}
}
for(int i=2; i<=N; i++) {
int k = i;
while (k > 1) {
fac[i].push_back(prime[k]); // 소인수 삽입
k /= prime[k];
}
}
/* 출력 코드
for (int i=2; i<=N; i++) {
cout << i << " = ";
for (int j = 0; j < fac[i].size(); j++) {
cout << fac[i].at(j) << " ";
}
cout << endl;
}
*/
return 0;
}
🔗 vector 컨테이너는 C++문법으로, [C++] vector container 정리 및 사용법와 C++ - 배열 vector와 2차원 vector 을 참고하면 좋다.
C언어에서는 2차원 배열이나 스택으로 구현할 수 있을 것 같다.
🔗 참고한 사이트:
소수 판별법 - 에라토스테네스의 체, 밀러-라빈(Miller-Rabin) 소수판별법
PS를 위한 정수론 - (1) 에라토스테네스의 체 활용
🐣 밀러-라빈 소수 판별법은 아직 이해가 잘 되지않아 정리하지 않았다. 더 실력이 늘어서 정리글을 작성하게 되는 날이 빨리 오길...
에라토스테네스의 체에 대해서도 이번 게시물에 적은 것 외 다른 활용법이 더 있다. 알고리즘 문제를 풀면서 천천히 학습할 예정이다.
그리고 이번 글을 작성하면서 C++의 벡터 컨테이너에 대해 알게됐는데, 참고한 글의 작성자분과 같은 반응이었다. ㅋㅋㅋ
자동으로 메모리를 할당해주고.. 알아서 끝에 들어가주고 알아서 삭제 해주고 다 알아서... 정말 소름 돋는다 푸하하!
효자 컨테이너란 말이 진짜 웃겼는데 진짜 공감되네...
최근에 막 C++ 공부를 시작했는데, 앞으로 계속 공부 중에 C랑 비교하면서 소름이 많이 돋을 것 같아서 조금 기대된다.