[알고리즘] 소수 만들기 시간복잡도 in Java

 
by 박신종



일반적인 방법에라토스테네스의 체, 비트연산자를 활용한 소수 만드는 알고리즘의 시간복잡도를 비교하고자 한다.

Last_Number = (10,000 ~ 1억) 을 대상으로 각 알고리즘 시간복잡도를 비교하였다.


구현 방법

일반적인 방법

가장 일반적인 방법이다.

제곱근까지의 숫자와 나눈 나머지 값이 0이라면 소수가 아니다.

for(int i=2; i<=LAST_NUM; i++) {
	boolean prime = true;
	for(int j=2; j*j<=i; j++) {
		if(i % j == 0) {
			prime = false;
			break;
		}
	}
	if(prime) System.out.print(i+" ");
}


에라토스테네스의 체

이름이 잘 외워지지 않는… 에라토스테네스의 체이다.

숫자 2부터 시작하여 시작 숫자를 제외한 배수의 숫자를 지워나가는 방법이다.

int[] nums = new int[LAST_NUM+1];
for(int i=2; i<=LAST_NUM; i++) {
	nums[i] = i;					// 각 위치에 숫자를 채워넣고,
}
for(int i=2; i*i<=LAST_NUM; i++) {
	if(nums[i] == 0) continue; 			// 이미 체크된 수의 배수는 확인하지 않음. 
	for(int j = i*i; j<=LAST_NUM; j+=i) 
		nums[j] = 0;				// i를 제외한 i의 배수들은 0으로 체크 
}
for(int i=2; i<=LAST_NUM; i++) {
	if(nums[i] != 0) {
		System.out.print(nums[i]+" ");
  	}	
}


비트마스크를 사용한 방법

에라토스테네스의 체를 비트마스크를 활용하여 구현한 방법이다.

비트마스크를 활용하여 하나의 원소에 8개의 자연수를 담아 소수여부를 파악하는 것이 핵심이다.

따라서, 필요한 배열의 크기를 1/8 줄일 수 있다.

// 비트연산자를 활용한 소수 만들기 
eratosthenes();
// 소수만 출력
for (int i = 0; i <= LAST_NUM; i++) {
	if (isPrime(i)) {
		System.out.print(i + " ");
	}
}


k »> 3은 사실상 k / 8과 같으며, 8개의 숫자를 한 원소에 담기 위한 인덱스 역할을 한다.

1 « ( k & 7 ) 는 k % 8과 같으며, 위에서 찾은 위치에서 8비트의 원하는 위치에 접근할 수 있다.

해당 비트가 1이라면 소수라고 판단할 수 있다.

이 외 메커니즘은 일반적인 에라토스테네스의 체와 같다.


public static char prime[] = new char[(LAST_NUM + 7) / 8 + 1];

public static boolean isPrime(int k) {
  return (prime[k >>> 3] & (1 << (k & 7))) == 0 ? false : true;
}

// 숫자 k가 소수가 아님을 마스킹(해당 비트에 0 설정)
public static void setComposite(int k) {
  prime[k >>> 3] &= ~(1 << (k & 7));
}

public static void eratosthenes() {
  java.util.Arrays.fill(prime, (char) 255); // 모든 비트를 1로 초기화
  setComposite(0); // 0과 1은 소수가 아님
  setComposite(1);
  int i, j;
  for (i = 2; i*i <= LAST_NUM; i++)
    if (isPrime(i))
      for (j = i*i; j <= LAST_NUM; j += i)
        setComposite(j);
}



각 방법의 시간복잡도 비교

숫자는 10,000 부터 100,000,000 (1억) 까지 10배씩 곱한 숫자를 대입하여 시간복잡도를 비교한다.

(10,000 이전의 숫자는 크기가 작아 비교불가)

시간은 Java의 System.currentTimeMillis() 메소드를 활용하였으며,

print는 생략하였다.

> 10,000 ( 1만 )

image

> 100,000 ( 10만 )

image

> 1,000,000 ( 100만 )

image

> 10,000,000 ( 1000만 )

image

> 100,000,000 ( 1억 )

image


결론

1) 1만 단위에서는 차이가 거의 없으나 숫자가 커질 수 록 일반적인 방법과 에라토스테네스 체의 방법 차이가 커짐을 알 수 있었다.

2) 1000만이 넘는 숫자에서는 에라토스테네스 체의 방법에서도 비트마스크를 활용하면 더욱 빠른 시간복잡도를 확인할 수 있다.

수치는 환경에 따라 달라질 수 있기 때문에 대략적인 수치만 감안하면 좋을 것 같다.