정수 n에 대하여 아래의 조건을 만족시키면 소수임을 알 수 있다.
2부터 n-1까지의 어떤 정수로도 나누어떨어지지 않는다.
먼저 어떤 정수 이하의 소수를 모두 나열하는 알고리즘을 직관적으로 풀어보자.
public class PrimeNumber {
public static void main(String[] args) {
int counter=0;
for(int n = 2; n <= 1000; n++) {
int i;
for(i = 2; i < n; i++) {
counter++;
if(n % i == 0)
break;
}
if(n == i)
System.out.println(n);
}
System.out.println("나눗셈을 수행한 횟수 : " + counter);
}
}
소수의 정의인 2~n-1까지의 정수로 나누어지는지 일일히 확인하는 코드이다.
counter로 나눗셈을 몇번 수행하는지 확인한다.
실행 결과 2~1000 사이의 소수를 찾기 위해 78022번의 나눗셈이 행해진다.
이제 이 알고리즘을 개선해보자.
알고리즘 개선 (1)
- n이 합성수인 경우 for문을 중단시킨다.
- 소수는 2부터 n-1까지의 어떤 소수로도 나누어떨어지지 않는다.
n이 2 또는 3으로 나누어떨어지지 않으면 2x2인 4 또는 2x3인 6으로도 나누어떨어지지 않는다.
무슨말이냐면 위 프로그램에서 n = 13일 때 나누는 수는 다음과 같다.
여기서 13은 2로 나누어지지 않으므로 당연히 4,6,8,10,12로도 나누어지지 않는다.
3으로도 나누어지지 않으므로 6,9로도 나누어지지 않는다.
이런식으로 하다보면 나눗셈을 11번이나 할 이유가 없는 것이다.
따라서 소수를 찾는 조건을 다음과 같이 수정할 수 있다.
2부터 n-1까지의 어떤 소수로도 나누어지지 않는다.
알고리즘을 개선한 코드는 다음과 같다.
public class PrimeNumber {
public static void main(String[] args) {
int counter = 0;
int ptr = 0;
int[] prime = new int[500]; // 소수를 넣어줄 배열
prime[ptr++] = 2; // prime[0] = 2를 미리 넣어준다.
for(int n = 3; n <= 1000; n += 2) { // 짝수는 소수가 될 수 없으므로 홀수만
int i;
for(i = 1; i < ptr; i++) {
counter++;
if(n % prime[i] == 0) // 나누어지면 소수가 아니므로 break
break;
}
if(ptr == i)
prime[ptr++] = n;
// ptr = i가 될 때까지 나누어지지 않으면 소수이므로 배열에 넣어준다.
}
for(int i = 0; i < ptr; i++)
System.out.println(prime[i]);
System.out.println("나눗셈을 수행한 횟수 : " + counter);
}
}
소수를 저장하는 배열 prime를 만들어 그 안에 있는 소수와 n값의 나눗셈을 시도한다.
소수로 판단되면 prime 배열에 넣어주고 반복한다.
코드 실행 결과 78022 -> 14622
알고리즘 개선 (2)
- 정수 n이 소수의 제곱근 이하일 때
100의 약수를 그림으로 나타내면 다음과 같다.
그림에서 나타내는 넓이가 100인 직사각형들 중 1~3번과 5~7번은 같은 직사각형이다.
그러니까 10 x 10 = 100이다.
10보다 큰 수와 y값을 곱해서 100이 될 수 없다. 무조건 y < 10이 된다.
10보다 작은 값으로 소수임을 검사했을 때 소수이면 소수이고 합성수이면 합성수가 된다.
그러므로 100의 제곱근 값인 10보다 작은 소수와 나누었을 때 나누어지지 않는다면 소수라고 할 수 있다.
따라서 소수를 찾는 조건을 다시 한번 수정할 수 있다.
n의 제곱근 이하의 어떤 소수로도 나누어떨어지지 않는다.
n의 제곱근을 계산하는 것보다 prime[]의 제곱을 계산하는 것이 더 쉬우므로 이것을 사용할 것이다.
알고리즘을 개선한 코드는 다음과 같다.
public class PrimeNumber {
public static void main(String[] args) {
int counter = 0;
int ptr = 0;
int[] prime = new int[500]; // 소수를 넣어줄 배열
prime[ptr++] = 2; // prime[0] = 2를 미리 넣어준다.
prime[ptr++] = 3; // prime[1] = 3을 미리 넣어준다.
for(int n = 5; n <= 1000; n += 2) { // 홀수만
boolean flag = false;
for(int i = 1; prime[i] * prime[i] <= n; i++) {
counter += 2; // 곱셈과 나눗셈 2번의 연산을 하므로 +2 해준다.
if(n % prime[i] == 0) { // 나누어지면 소수가 아니다. flag = true
flag = true;
break;
}
}
if(!flag) { // flag = false이면 소수이므로 prime[]에 넣어준다.
prime[ptr++] = n;
counter++;
}
}
for(int i = 0; i < ptr; i++)
System.out.println(prime[i]);
System.out.println("곱셈과 나눗셈을 수행한 횟수 : " + counter);
}
}
중간에 counter += 2; 인 이유는
곱셈 prime[i] * prime[i] 와 나눗셈 n % prime[i] 두 연산의 수행 횟수를 계산하기 위해서이다.
prime[i] * prime[i] <= n 이 성립하지 않는 경우 n % prime[i]를 수행하지 않으므로 counter는 +1만 해준다.
코드 실행 결과 78022 -> 14622 -> 3774 로 수행 횟수가 확연히 줄어든 것을 볼 수 있다.
'알고리즘 > 자료구조와 알고리즘' 카테고리의 다른 글
[Algorithm] 중복된 문자 제거하기 for JAVA (0) | 2021.11.05 |
---|---|
[Algorithm] 탐욕(그리디) 알고리즘 (greedy algorithm) (0) | 2021.09.30 |
[Algorithm] 이진 검색 알고리즘 (0) | 2021.09.13 |
[Algorithm][Java] 기수로 변환하기 (0) | 2021.08.18 |
[Algorithm][Java] 배열 안 요소를 역순으로 정렬하기 (0) | 2021.08.17 |