소수 알고리즘 자바 - sosu algolijeum jaba

JAVA [자바] - 소수 구하는 알고리즘 및 구현

  • 2020.04.15 00:15
  • 알고리즘/Java

  • 들어가기 전에

소수 [Prime Number]

소수의 정의는 1보다 큰 자연수 중 1 과 그 수 자기 자신만을 약수로 갖는 자연수를 의미한다는 점은 누구나 알고 있을 것이다.

즉, 소수의 약수는 2개만을 갖고, 그 중 하나는 반드시 1 이며, 나머지는 자기 자신만을 약수로 갖기 때문에 만약 1 보다 크고 자기 자신의 수보다 작은 자연수를 약수로 갖게 된다면 이는 합성수라고 한다.

그리고 위의 개념을 확장해본다면 합성수는 3개 이상인 자연수의 곱으로 이루어져 있으며 소수로 이루어진 곱으로 표현할 수 있다.

그리고 이를 소인수분해라고 한다.

더욱 개념을 넓혀볼까.

소수는 1과 자기 자신만을 약수로 갖고, 합성수는 3개 이상인 자연수의 곱으로 이루어져 있으며 소수들로 이루어진 곱으로 표현할 수 있다고 했다.

1 보다 큰 자연수는 모두 소수 또는 합성수로 이루어져 있으니 1 보다 큰 모든 자연수는 소수들의 곱으로 표현할 수 있다.

이렇듯 소수는 수학적으로도 매우 중요한 개념이다.

그렇다면 왜 개발자 입장에서 또는 프로그래밍의 세계에서 소수가 중요한 이유는 무엇일까.

바로 "암호" 때문이다.

실제로 우리 일상생활에서도 많이 쓰이는 암호 또한 소수를 이용하고 있다.

대표적으로 'RSA 암호화 방식'이 있다.

위 암호화 방식의 가장 근본적인 접근 방식은 이렇다.

"임의의 수들의 곱은 구하기 쉽지만 역으로 소인수 분해하는 것은 어렵다."

즉, 𝑝 × 𝑞 = 𝐍 를 만들기는 쉽지만,  𝐍 을 역으로 소인수 분해하여 𝑝 × 𝑞 를 만족하는 수를 찾기가 어렵다는 것이다.

예로 들어보자.

77 을 소인수분해 하면?

7 × 11 이 바로 나온다.

그렇다면 조금 더 큰 수라면 어떨까?

12126 을 소인수 분해 해보자.

먼저 직관적으로는 2로 나누어 떨어지겠다.

  P × 2 = 12126      ...     P = 6063

그리고 각 자릿수의 합 ( 6 + 0 + 6 + 3 ) 은 15 로 3의 배수이니 3으로 나누어 떨어지겠다.

  P × 2 × 3 = 12126     ...     P = 2021

그리고 2021 이 수를 소인수 분해를 해보자.

한 번에 눈에 들어오는가?

2021 을 소인수 분해하면 43 과 47 이다.

즉, 2부터 하나씩 대입해보려 하면 43 까지 대입해보아야 한다.

그리고 43과 47 은 각각 소수로 더이상의 소인수분해가 불가능 하다.

뭐 이정도 까진.. 컴퓨터의 대단한 연산능력에 맏기면 금방 풀릴 수 있겠다.

그런데 다음과 같은 수라면??

114381625757888867669235779976146612010218296721242362562561842935706935245733897830597123563958705058989075147599290026879543541

이 수는 실제로 RSA-129 의 공개키였고 현재는 풀린 129자리의 수다.

위 수를 여러분의 컴퓨터로 소인수 분해를 할 수 있겠는가?

일단 정답부터 말하자면 다음과 같다.

RSA-129 =

3490529510847650949147849619903898133417764638493387843990820577 × 32769132993266709549961988190834461413177642967992942539798288533

위의 두 수는 반드시 소수다.

위 문제를 컴퓨터로 푼다면 쉽게 풀릴 것 같지만 이는 오산이다.

실제로 위 문제를 풀기위해서 약 1600대의 컴퓨터와 600명의 사람이 모여 6개월동안 진행했다.

이 마저도 129자리 소수를 푸는데 저러한 시간이 걸렸는데 현재 암호방식은 RSA-2048로 617 자리수의 공개키가 있다.

617 자리의 수는 얼마나 오래 걸릴지 가늠이 안된다.

그래서 실제로도 대부분의 인터넷뱅킹도 RSA-2048 을 쓰고 있다.

혹시 독자 분들 중에 위 키를 푼다면 혹시 모를까... 세계 유명인사가 될 테니..

RSA-2048 의 공개키를 공유하겠다.

RSA-2048 =

25195908475657893494027183240048398571429282126204032027777137836043662020707595556264018525880784406918290641249515082189298559149176184502808489120072844992687392807287776735971418347270261896375014971824691165077613379859095700097330459748808428401797429100642458691817195118746121515172654632282216869987549182422433637259085141865462043576798423387184774447920739934236584823824281198163815010674810451660377306056201619676256133844143603833904414952634432190114657544454178424020924616515723350778707749817125772467962926386356373289912154831438167899885040445364023527381951378636564391212010397122822120720357

(화이팅입니다...)

서론이 너무 길었다.. 여튼 소수는 우리 일상생활에서도 긴밀하게 쓰이고 있다.

그런김에 소수를 구하고 판별하는 몇가지 방법을 같이 배워보고자 한다.

각 방법마다 소수를 판별하는 알고리즘과,

N 이하의 소수를 모두 구하는 알고리즘을 소개하겠다.


  • 방법 1

" N 보다 작은 자연수들로 모두 나눠본다. "

가장 기본적인 방법이라 할 수 있겠다.

임의의 수 N 이 1 과 N 을 제외한 다른 수를 약수로 갖고 있다면 그 수는 소수가 아니고, 만약 다른 약수가 없다면 그 수는 소수일 것이다.

[ 소수 판별 알고리즘 ]

import java.util.Scanner; public class Prime_1 { public static void main(String[] args) { Scanner in = new Scanner(System.in); is_prime(in.nextInt()); } // 소수 판별 메소드 public static void is_prime(int number) { // 0 과 1 은 소수가 아니다 if(number < 2) { System.out.print("소수가 아닙니다"); return; } // 2 는 소수다 if(number == 2) { System.out.print("소수입니다"); return; } for(int i = 2; i < number; i++) { // 소수가 아닐경우 if(number % i == 0) { System.out.print("소수가 아닙니다"); return; } } // 위 반복문에서 약수를 갖고 있지 않는경우 소수다. System.out.print("소수입니다"); return; } }

2 이상 N 미만의 수 중에 나누어 떨어지는 수가 존재한다면 소수가 아님을 이용한 소수 판별법이다.

또한, 위 알고리즘의 시간복잡도는 당연히 N 이하의 수까지 모든 수를 검사하므로 O(N) 이다.

[ N 이하의 모든 소수 구하는 알고리즘 ] 

위를 응용하여 N 이하의 소수를 모두 구하는 알고리즘은 다음과 같을 것이다.

// 소수만 출력 import java.util.Scanner; public class Prime_1 { public static void main(String[] args) { Scanner in = new Scanner(System.in); int N = in.nextInt(); // 0 ~ N 까지 수 중 소수를 구하는 반복문 for(int i = 0; i <= N; i++) { make_prime(i); } } // 소수 생성 메소드 public static void make_prime(int number) { // 0 과 1 은 소수가 아니므로 종료 if(number < 2) { return; } // 2 는 소수다 if(number == 2) { System.out.println(number); return; } for(int i = 2; i < number; i++) { // 소수가 아닐경우 종료 if(number % i == 0) { return; } } // 위 반복문에서 약수를 갖고 있지 않는경우 소수다. System.out.println(number); return; } }

위 알고리즘은 소수 판별 알고리즘을 N 번 반복하여 각 수마다 소수 판별 한 뒤 소수만 출력하도록 한 알고리즘이다.

즉 O(N) 알고리즘을 N 번 반복하므로 위 방법의 N 이하의 소수를 모두 구하는 알고리즘의 시간복잡도는 O(N²) 이다.


  • 방법 2

" √N 이하의 자연수들로 모두 나눠본다. "

방법 1 의 방법에서 약간 업그레이드 된 알고리즘이다.

바로 N 을 √N 이하의 자연수들만 나누는 방법이다.

√N 이하의 자연수들만 나누면 되는지는 생각보다 매우 쉽게 알 수 있다.

생각해보자. 소수를 판별한다는 것은 결국 1 과 자기 자신을 제외한 다른 자연수를 약수로 갖고 있으면 안된다는 의미다.

 임의의 자연수 𝐍 (𝐍 > 0) 이 있다고 가정하자.

 𝑝 × 𝑞 = 𝐍 을 만족할 때 우리는 아래와 같은 부등식을 완성할 수 있다.

( 1 ≤  𝑝 , 𝑞 ≤ 𝐍 )

그리고 𝑝 와 𝑞 중 하나는 √N 보다 작거나 같다.

예로들어  𝐍 = 16 라고 하자.

그러면 아래와 같이 두 수의 곱으로 표현할 수 있다.

1 × 16

2 × 8

4 × 4

8 × 2

16 × 1

여기서 볼 수 있듯이 만약 𝑝 가 증가한다면 자연스레 𝑞 가 감소하고,

반대로 𝑝 가 감소한다면 자연스레 𝑞 가 증가한다.

그리고 𝑝 와 𝑞 는 𝐍의 약수이기 때문에 결국 𝐍 을 임의의 수로 나누게 되면 임의의 수가 √N 보다 작다면 결국 나머지는 √N 보다 클 수 밖에 없다.

결과적으로 𝑝 와 𝑞 중 하나는 반드시 √N 보다 작거나 같다.

즉, √N 이하의 자연수 중에 나누어 떨어지는 수가 있다면 이는 1 과 N 을 제외한 다른 자연수가 N 의 약수라는 의미이므로 소수가 아니게 되는 것이다.

그럼 이를 토대로 알고리즘을 짜보자.

[ 소수 판별 알고리즘 ]

import java.util.Scanner; public class Prime_2 { public static void main(String[] args) { Scanner in = new Scanner(System.in); is_prime(in.nextInt()); } // 소수 판별 메소드 public static void is_prime(int number) { // 0 과 1 은 소수가 아니다 if(number < 2) { System.out.print("소수가 아닙니다"); return; } // 2 는 소수다 if(number == 2) { System.out.print("소수입니다"); return; } // 제곱근 함수 : Math.sqrt() for(int i = 2; i <= Math.sqrt(number); i++) { // 소수가 아닐경우 if(number % i == 0) { System.out.print("소수가 아닙니다"); return; } } // 위 반복문에서 약수를 갖고 있지 않는경우 소수다. System.out.print("소수입니다"); return; } }

2 이상 √N 이하의 수 중에 나누어 떨어지는 수가 존재한다면 소수가 아님을 이용한 소수 판별법이다.

또한, 위 알고리즘의 시간복잡도는 당연히 √N 이하의 수까지 모든 수를 검사하므로 O(√N) 이다.

[ N 이하의 모든 소수 구하는 알고리즘 ] 

위를 응용하여 N 이하의 소수를 구하는 알고리즘은 다음과 같을 것이다.

// 소수만 출력 import java.util.Scanner; public class Prime_2 { public static void main(String[] args) { Scanner in = new Scanner(System.in); int N = in.nextInt(); // 0 ~ N 까지 수 중 소수를 구하는 반복문 for(int i = 0; i <= N; i++) { make_prime(i); } } // 소수 생성 메소드 public static void make_prime(int number) { // 0 과 1 은 소수가 아니므로 종료 if(number < 2) { return; } // 2 는 소수다 if(number == 2) { System.out.println(number); return; } // 제곱근 함수 : Math.sqrt() for(int i = 2; i <= Math.sqrt(number); i++) { // 소수가 아닐경우 종료 if(number % i == 0) { return; } } // 위 반복문에서 약수를 갖고 있지 않는경우 소수다. System.out.println(number); return; } }

위 알고리즘은 소수 판별 알고리즘을 N 번 반복하여 각 수마다 소수 판별 한 뒤 소수만 출력하도록 한 알고리즘이다.

즉 O(√N) 알고리즘을 N 번 반복하므로 위 방법의 N 이하의 소수를 모두 구하는 알고리즘의 시간복잡도는 O(N√N) 이다.


  • 방법 3 : 에라토스테네스의 체

소수를 구하는 대표적인 방법 중 하나다.

" k=2 부터 √N 이하까지 반복하여 자연수들 중 k를 제외한 k의 배수들을 제외시킨다"

즉, 방법으로 보면 다음과 같다.

소수 알고리즘 자바 - sosu algolijeum jaba

k = 2 이면 2 를 제외한 2의 배수를 모두 지워주고,

k = 3 이면 3 을 제외한 3의 배수를 모두 지워주고,

(4는 이미 k = 2 에서 제외되어 넘어간다.)

k = 5 이면 5 를 제외한 5의 배수를 모두 지워주고..

이렇게 하여 k = √N 까지 반복하는 방법이다.

이 방법은 소수를 구하는 방법이랑 판별하는 방법이 동일하기 때문에 하나로 묶어서 설명하겠다.

[ N 이하의 모든 소수를 구하는 알고리즘 ] 

import java.util.Scanner; public class Prime_3 { public static boolean[] prime; // 소수를 체크할 배열 public static void main(String[] args) { Scanner in = new Scanner(System.in); int N = in.nextInt(); make_prime(N); for(int i = 0; i < prime.length; i++) { if(prime[i] == false) { // 소수(false)일 경우 출력 System.out.println(i); } } } // N 이하 소수 생성 메소드 public static void make_prime(int N) { prime = new boolean[N + 1]; // 0 ~ N /* 소수가 아닌 index = true 소수인 index = false */ // 2 미만의 N 을 입력받으면 소수는 판별할 필요 없으므로 바로 return if(N < 2) { return; } prime[0] = prime[1] = true; // 제곱근 함수 : Math.sqrt() for(int i = 2; i <= Math.sqrt(N); i++) { // 이미 체크된 배열이면 다음 반복문으로 skip if(prime[i] == true) { continue; } // i 의 배수들을 걸러주기 위한 반복문 for(int j = i * i; j < prime.length; j = j+i) { prime[j] = true; } } } }

언뜻 보기에는 이중for문이라 시간복잡도가 O(N²) 일 것 같지만 그렇지 않다.

1 ~ 𝑥 까지의 수가 있는 칸을 체크하는 횟수를 대략적으로 따진다면 아래와 같을 것이다.

(n = 1 부터 시작한다고 할 때)

(𝑥) + (𝑥/2) + (𝑥/3) + (𝑥/4) + (𝑥/5) + (𝑥/6) + ⋯ + 1

그러면 위 식을 다음과 같이 표현 할 수 있다.

𝑥(1 + 1/2 + 1/3 +  ⋯ + 1/𝑥) 

위의 x로 묶인 괄호 안의 수열은 조화 수(Harmonic Number)라고 하는데, 쉽게 말해서 조화 수열에서 부분 합을 말한다고 생각하면 된다.

그리고 다음과 같이 발산하게 된다고 한다.

소수 알고리즘 자바 - sosu algolijeum jaba

이 때 감마는 상수 값이고 O(1/x)는 우리가 생각하는 big O와 같은 표기로 수학에서는 함수 성장률의 상한선이다. 그리고 위의 조화수는 대략 자연로그의 형태를 따라간다.

(자세한 건 아래 링크를 참고하시기 바란다.)

https://en.wikipedia.org/wiki/Harmonic_number

즉, N 이하의 소수에 대하여 체에 거르는 시간이 logN 이므로 단순하게 체에 거르는 것만 해도 시간 복잡도는 O(N㏒N) 이다.

( ㏒ 는 자연로그 𝑙𝑛 으로 본다 )

그런데 우리는 여기서 더 나아가 이미 체크된 배열은 검사하지 않고 다음 반복문으로 넘어간다.

(𝑥/2) + (𝑥/3) + (𝑥/4) + (𝑥/5) + (𝑥/6) (𝑥/7)(𝑥/8)+ (𝑥/9) ⋯ + (𝑥/𝑥-1) 

우리는 앞서 조화수를 통해 점근적 시간 복잡도 O(NlogN) 이라는 시간 복잡도를 얻었다.

이 의미는 x개의 수에 대해 2일 때 체크하는 개수인 (x/2), 3일 때 체크하는 개수인 (x/3), ... 이렇게 체크를 하게 된다.

하지만 우리가 알고싶은 것은 이미 중복되는 수들은 검사하지 않는다는 것이다. 이 의미는 무엇일까? 결국 검사하는 수는 소수로 판정 될 때 그의 배수들을 지우는 것이라는 것이다. 이 말은 구간 내의 소수의 개수를 알아야 한다는 뜻이기도 하다.

근데, 소수가 규칙성이 있는가? 이 것은 아직까지도 풀지못한 것이다. 이를 찾고자 가우스는 15살 때 하나하나씩 구하면서 x보다 작거나 같은 소수의 밀도에 대해 대략 1 / ln(x) 라는 것을 발견하게 되는데 증명을 못했다. (이를 가우스의 소수 정리라고 한다)

즉, 위를 거꾸로 말하면 x번째 소수는 xlog(x) 라는 의미가 되지 않겠는가? 즉, 우리는 앞서 1/x 의 합을 구했지만, 실제로 중복되는 수가 제외된다면 x는 소수만 된다는 의미고, 이는 소수의 역수 합이다. (1/2 + 1/3 + 1/5 + 1/7 + ⋯ ) 이런식으로 말이다.

그러면 시간 복잡도를 도출해낼 수 있다.

바로 O(Nlog(log N)) 의 시간 복잡도를 갖게 된다.

[소수에 대한 심화 내용]

더보기

이왕 소수 이야기가 나온김에 위에서 수식을 정리한 것을 이용하여 조금만 확장을 해보자.

앞서 구한 조화수를 다시 갖고와보자.

sum = (1 + 1/2 + 1/3 + 1/4 + 1/5 + 1/6 + ⋯ ) 

위 식을 T 라고 놓고 무한히 놓여있다는 가정하에 몇 가지 이리저리 만져보자.

소수 알고리즘 자바 - sosu algolijeum jaba

그리고 위 식에서 1/2를 양변에 곱해보자.

소수 알고리즘 자바 - sosu algolijeum jaba

그리고 (1)번 식에서 (2)번 식을 빼 보자.

소수 알고리즘 자바 - sosu algolijeum jaba

이렇게 우변에 분모는 홀수만 남게 되었다.

그럼 분모 3에 대해서도 양변을 나눠서 해본다면 어떻게 될까? 다음과 같이 될 것이다.

소수 알고리즘 자바 - sosu algolijeum jaba

이런식으로 진행하다보면 좌변에는 항상 (1 - (1/소수)) 만 오게 된다는 것을 알 수 있다.

즉, 모든 소수에 대해 위 과정을 반복하면 다음과 같은 등식이 나올 것이다.

소수 알고리즘 자바 - sosu algolijeum jaba

그리고 위를 정리하면 다음과 같이 표현 할 수도 있다.

소수 알고리즘 자바 - sosu algolijeum jaba

자 위 식의 경우 지수를 모두 -1으로 고정해놓고 정리했지만, 만약 지수도 어떤 집합 내의 수로 본다면 어떻게 정리할 수 있을까?

바로 다음과 같은 수식으로 정리할 수 있다.

소수 알고리즘 자바 - sosu algolijeum jaba
n은 자연수, s는 지수, p는 소수를 의미

만약 수학에 관심이 있는 분이라면 이 공식을 보면 뭔가 익숙하다는 느낌을 받을 수도 있을 것이다.

그렇다. 바로 리만-제타 함수 식다.

소수 알고리즘 자바 - sosu algolijeum jaba

위 식에서 s를 복소수로 확장하면, 이 함수를 0으로 만드는 실수부는 1/2 이다. 라는 가설이 그 유명한 '리만 가설'이다. (솔직히 필자도 그렇다고만 알고있지 수학자가 아니라 자세하게는 모른다..)이에 대해 더 나아가면 필자도 모르는 내용이고 주제와는 완전 다른 곳으로 향할 것 같으니... 궁금한 분은 아래 유튜브를 참고하시기를 바란다. (필자가 본 수학 유튜버 중 가장 최고의 유튜버라고 생각한다..)

https://youtu.be/sD0NjbwqlYw

소수 알고리즘 자바 - sosu algolijeum jaba

우리는 앞서 에라토스테네스의 체에서 가우스의 소수 정리 부분 중 소수 밀도라는 얘기를 잠깐 했었다. 여기서 하나 정의하고 있는 것이 바로 π(x) 라는 함수다. (소수 계량 함수라고도 한다.)

이 함수는 x 이하의 수 중 소수의 개수를 표현하는 함수라고 정의하고 있다. 예로들어 아래와 같다.

π(1) = 0, π(2) = 1, π(3) = 2, π(4) = 2 ... 

 그리고 앞서 소수 정리에서 x 이하의 수에 대해 어떤 정수가 소수일 확률, 즉 밀도는 1 / ln(x) 라고 했다.

즉, 위 가정이 맞다고 할 때, x까지의 모든 소수의 개수는 x / ln(x) 가 될 것이다.

그럼  π(x) 와 x / ln(x) 는 같아야 할 것이다. 즉, 아래와 같은 식이 될 것이다.

소수 알고리즘 자바 - sosu algolijeum jaba

위 식이 바로 소수 정리의 메인이다. (x / ln(x) 를 로그 적분 함수로 li(x) 라고도 한다.) 

하지만, x / ln(x) 즉,  분명하게 실제 소수의 개수인 π(x)와 li(x) 와는 차이가 존재한다. 바로 이 차이를 줄이는 것이 관건인 것이고 리만 제타 함수는 π(x) 를 구하기 위해 정의한 것이라고 보면 된다.

즉, 더욱 정확하게 소수의 분포를 알아내기 위해 나온 것이 리만 제타 함수고, 여기서 좀 더 확장하여 나온 하나의 가설이 바로 리만가설이라는 것이다. (복소평면에서 비자명근 어쩌고 저쩌고 하는데.. 사실 모두 이해하기에는 저도 너무 어렵네요..)

이렇게 잡지식을 하나 추가해보았다.

참고할만한 풀이 링크는 아래에 기재해두겠다.

https://www.geeksforgeeks.org/how-is-the-time-complexity-of-sieve-of-eratosthenes-is-nloglogn/?ref=rp

How is the time complexity of Sieve of Eratosthenes is n*log(log(n))? - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

소수 알고리즘 자바 - sosu algolijeum jaba

  • 성능 분석

 N 이하의 소수를 모두 구하는 시간 복잡도에 대해서 대락 아래와 같았다.

방법 1 : O(N²)

방법 2 : O(N√N) 

방법 3 : O(N㏒(㏒N))

그래프로 보면 대략적으로 다음과 같다.

소수 알고리즘 자바 - sosu algolijeum jaba

초록색 : 방법 1

노랑색 : 방법 2

보라색 : 방법 3

그럼 실제로도 그런지 위에서 짠 코드를 활용하여 테스트를 해보자.

각각 1만, 5만, 10만, 50만, 100만, 500만, 1000만의 케이스가 주어지고 해당 케이스 수 이하의 모든 소수를 구할 때, 시간이 얼마나 걸리는지 한 번 테스트를 해보자.

코드는 아래와 같이 했으니 참고바란다.

// 소수만 출력 import java.util.ArrayList; public class Prime_1 { public static ArrayList<Integer> list; public static ArrayList<Integer> list2; public static ArrayList<Integer> list3; public static void main(String[] args) { int N = 10000; for (int i = 0; i < 5; i++) { System.out.println("Prime Numbers less than or equal to " + N); list = new ArrayList<Integer>(); // list 초기화 make_prime(N); list.clear(); // 리스트 비우기 list2 = new ArrayList<Integer>(); // list 초기화 make_prime2(N); list2.clear(); // 리스트 비우기 list3 = new ArrayList<Integer>(); // list 초기화 make_prime3(N); list3.clear(); // 리스트 비우기 System.out.println(); if(i % 2 == 0) { N *= 5; } else { N *= 2; } } } // 방법 1 : N 이하의 모든 소수 public static long make_prime(int number) { final long start = System.nanoTime(); boolean TrueFalse; // true : 소수 , false : 비소수 for (int i = 3; i <= number; i++) { // 2는 소수이므로 3 부터 시작 // 0 과 1 은 소수가 아니므로 skip TrueFalse = true; for (int j = 2; j < number; j++) { // 소수가 아닐경우 종료 if (i % j == 0) { TrueFalse = false; break; } } // 위 반복문에서 약수를 갖고 있지 않는경우 소수다. if (TrueFalse) list.add(i); } final long end = System.nanoTime(); System.out.println("run1 : " + (end - start) * 1e-9 + " sec"); return end - start; } // 방법 2 : N 이하의 모든 소수 public static long make_prime2(int number) { final long start = System.nanoTime(); boolean TrueFalse; // true : 소수 , false : 비소수 for (int i = 3; i <= number; i++) { // 2는 소수이므로 3 부터 시작 // 0 과 1 은 소수가 아니므로 skip TrueFalse = true; for (int j = 2; j <= Math.sqrt(number); j++) { // 소수가 아닐경우 종료 if (i % j == 0) { TrueFalse = false; break; } } // 위 반복문에서 약수를 갖고 있지 않는경우 소수다. if (TrueFalse) list2.add(i); } final long end = System.nanoTime(); System.out.println("run2 : " + (end - start) * 1e-9 + " sec"); return end - start; } // 방법 3 : N 이하의 모든 소수 public static long make_prime3(int number) { final long start = System.nanoTime(); boolean[] check = new boolean[number + 1]; // true : 비소수 , false : 소수 check[0] = check[1] = true; for (int i = 2; i <= Math.sqrt(number); i++) { // // 0 과 1 은 소수가 아니므로 skip if (check[i]) continue; // 소수가 아닐경우 skip for (int j = i * i; j < check.length; j += i) { check[j] = true; } } for (int i = 0; i < check.length; i++) { if (!check[i]) list3.add(i); // 소수(flase)인 경우 list3 에 추가 } final long end = System.nanoTime(); System.out.println("run3 : " + (end - start) * 1e-9 + " sec"); return end - start; } }

한 번 기회가 된다면 직접 코드를 돌려보는 것을 추천드린다.

결과는 다음과 같다.

소수 알고리즘 자바 - sosu algolijeum jaba

확연히 보기에도 에라토스테네스의 체가 압도적으로 빠르다는 것을 볼 수 있다.

실제로도 1억 이하의 모든 소수를 구하더라도 불과 몇 초 안걸린다.

소수 알고리즘 자바 - sosu algolijeum jaba

  • 정리

오늘은 소수에 대해 약간의 지식과

소수 판별법, 소수 구하는 방법을 알아보았다.

결과만 말하자면 대부분의 소수 문제는 에라토스테네스의 체를 쓰면 거의 대부분이 풀린다.

알고리즘도 복잡하지 않으면서 매우 좋은 성능을 보여주기 때문이다.