알고리즘 :: 최적화된 에라토스테네스의 체

오늘은 코드 전체를 알려드리진 않을 생각입니다. 어차피 여기저기서 쉽게 알 수 있는 유명한 코드이고, 스스로 한 번 생각해 보시라는 의미에서요. 이번 글이 시리니 님의 글과 비교해서 진행 되므로 시리니 님의 글에 나와 있는 코드를 참조하시는 것도 도움이 될 것 같습니다.

에라토스테네스의 체

우선, 시리니 님의 글에 에라토스테네스의 체에 대해 잘 정리 되어있으니 살짝 인용 좀 해볼까요? 좋은 알고리즘을 소개해주시는 시리니 님께 언제나 감사드립니다. ^^

  1. 2부터 N까지를 크기로 하는 체(여기서는 배열)를 만든다.
  2. 우선 모든 배열 요소를 1로 채운다.
  3. 2를 제외한 2의 배수를 체(배열)에서 모두 제거(즉, 해당 배열 요소들을 모두 0으로 설정)한다.
  4. 3을 제외한 3의 배수를 … (위 3과 동일)
  5. 위의 절차를 N까지 반복해서 그래도 1로 끝까지 버텨낸(?) 배열의 각 인덱스 숫자가 바로 소수다.

간단히 정리해서, 소수와 소수 아닌 수 모두를 체에 담은 후에 소수 아닌 수만 걸러낸다고 보시면 됩니다.

그리고 2번 과정에서 배열 요소를 1로 채운다는 의미는 TRUE로 채운다는 의미입니다. 코드 상에서 조금 더 가독성을 향상시키려면,

typedef enum {FALSE, TRUE} BOOL;


로 미리 선언한 후에 배열을 BOOL형으로 정의합니다. 이렇게 하면 1과 0 대신에 TRUE, FALSE로 이용할 수 있습니다. C++ 에서는 bool형을 직접 지원하는데 위의 BOOL이 4byte인데 비해 bool은 1byte 자료형입니다.

최적화

이제 이 글의 핵심인 최적화 코드에 대해 알아 봐야겠죠?

사용자 삽입 이미지
위 그림은 시리니 님의 글에 소개된 코드에 의한 결과입니다. 쓸데없이 백 만까지 구해봤는데요. 흠… 22초 걸렸네요. 이 시간을 줄이는 게 이 글의 목적입니다.

우선 시리니 님의 글에 소개된 코드 중에서 일부를 보겠습니다.

// i 에 2 배한 값 j 를 i 로 나눠 0 이 되는지 (즉 소수가 아닌지) 보고
// 또 그 j 에 1을 더한 3 을 i 에 곱해서 다시 i 로 나눠 0 이 되는지 보고... (반복)
for (j = 2*i; j <= Num; j++) {
    // 나눠 떨어지는 수가 있다면 그 놈은 이미 소수가 아니다!
    if (j % i == 0) prime[j] = 0;
}


위 코드에 대해서는 주석에 자세히 나와 있습니다(시리니 님 멋있어요^^). 그런데 j++가  눈에 띄네요. 범위가 작다면 문제가 되지 않지만 큰 범위에서는 1씩 더해가며 나눠 떨어지는지 않은지 일일이 비교하는 게 고달프죠. 그러면 어떻게 해야할까요?

어차피 소수로 판정된 수의 배수들은 소수가 아닙니다. 그렇다면 이 배수들만 걸러내주면 알아서 다 정리되겠지요? 아래 코드처럼요.

for(j = i*2; j <= Num; j += i)
{
    prime[j] = FALSE;
}


이미 i가 소수임을 확인한 상태에서 i의 2배수인 j와 그 배수들은 모두 소수가 아닙니다. 그러므로 j++이 아닌 j+=i로 증가문을 지정해주는 것이죠. 그리고 소수인지 아닌지 비교할 필요도 없으므로 바로 소수가 아니라고 FALSE로 알려줍니다.

에이! 이거나 저거나 얼마나 차이 난다고… 하실 것 같아 그 결과를 보여 드리겠습니다.

사용자 삽입 이미지
짜잔! 1초! 와우! GooD! 이 정도면 쓸만하죠?

그런데 사실, 범위가 백 만까지 가서 이런 차이를 보이는 것이지, 그보다 작은 범위에서는 별 차이가 안 납니다. 그리고 연산을 수행한 제 컴퓨터가 팬티엄2라는 것도 고려해야지요. 그렇더라도 이렇게 최적화할 수 있다는 것을 보여줄 수 있기에 전 이 글이 의미 있다고 생각합니다.

음.. 도움 되셨죠? 그래야하는데… ^^;

참고 문헌

범위 내 소수만 걸러내기 (에라토스테네스의 체) :: http://sirini.net/blog/?p=738
알고리즘 :: 소수인지 판별하는 알고리즘을 최적화하자. :: /?p=754

Similar Posts

  • imagEmail, 간략한 기획

    비주얼 베이직으로 만들었던 hisAlpha Image를 C 언어를 이용하여 리눅스용으로 다시 만들려고 합니다.  리눅스용이라고는 하나, 윈도우나 OS X용으로도 쉽게 포팅될 듯합니다. hiaAlpha Image는 일종의 프로토타입인 것이죠. 사실, 강분도 님의 권유가 아니었으면 그냥 묻혔을겁니다. ㅋ 아래는 간략한 기획입니다. 기능 이메일 주소를 이미지로 만듬. 15종의 메일서버 지원. 개발환경 환경: 리눅스 (Ubuntu 8.10, i386) 언어 및 툴킷 : C,…

  • C :: [열혈강의 C 프로그래밍] p.115 char_add.c

    tothefelix 님의 블로그에서 아래의 코드를 가져왔습니다. 언뜻 보면 정상적인 코드인 것 같으나, 유심히 보면 자료형이 잘못된 것을 알 수 있습니다. 1바이트의 char 형 변수에 4바이트의 int 형 값을 넣는 오류입니다. 명백히 코드 상의 오류지요. 정말 열혈강의에 저런 코드가 예문으로 나와 있다면 무척 실망입니다. 역시 국내 C 레퍼런스로는 김상형 님의 “혼자 연구하는 C/C++“가 최고의 레퍼런스가 아닌가…

  • imagEmail, GUI 구현

    GTK+와 Glade 3로 구현한 GUI입니다. 이제 여기에 C 코드로 알고리즘만 얹어 놓으면 됩니다. windowMain windowMain은 이름 그대로 메인 화면입니다. 오른쪽의 드롭다운 버튼으로 이메일의 서버종류를 선택할 수 있습니다. 우측 상단의 x 버튼으로 프로그램을 종료합니다. dialogSave dialogSave는 파일을 저장할 때 나오는 대화상자입니다. 아직 100% 구현되지 않았는데, 다 구현되면 “저장”, “취소” 버튼 위에 이메일 이미지가 어떻게 나오는지 보여줄…

  • C :: typedef으로 함수 포인터를…

    Dll Injection으로 Brute Force를 하려고 공부하던 차에 함수 포인터를 활용하는 방법을 알게 되었습니다. 일반적으로 C 언어는 절차 지향 언어, 함수 지향 언어라고 알려져 있어서 C++의 클래스 구현이 안 된다고 배웁니다만, 이 함수 포인터를 이용하면 충분히 클래스를 구현할 수 있을 것 같습니다. 실제로 커널 등의 여러 소프트웨어에서 이 방법을 쓰는 것 같구요. 함수 포인터를 선언하려면 우선…

  • 알고리즘 :: XOR 연산을 이용하여 스왑 알고리즘을 최적화하자.

    한동훈 님의 「프로그래밍 스타일」이라는 글을 읽다가 스왑(Swap) 알고리즘에 대한 코드가 있어서 소개합니다. 아시는 분들도 많겠지만, 모르는 분들도 많을거라 생각되네요. 흔히 두 값을 맞바꿀 때 이런 알고리즘을 사용합니다. 변수 temp를 하나 더 선언해서 중간매개체로 사용하죠. 이 때문에 4byte의 메모리를 더 차지합니다. 하지만 아래에 소개할 알고리즘은 추가적인 메모리가 필요 없습니다. 전 이 알고리즘을 처음 보고 와우! 하고…

  • DLL Injection 은 어떻게 이루어지는가? – DLL? 그게 뭐야?

    루트킷을 비롯하여 바이러스, 악성코드 등 여러 분야에 두루 쓰이는 기법이 DLL Injection입니다. 윈도우즈 OS에 한정되어 적용되는 것이지만, 윈도우즈 자체의 점유율이 높은 이유로 아주 효과적으로 공격자가 원하는 작업을 수행할 수 있는 방법이죠. 최근 루트킷에 대해 공부하면서 이 DLL Injection이 어떻게 이루어지는 알게 된 것을 정리해봅니다. DLL? 그게 뭐야? DLL은 윈도우즈 OS에서 사용되는 동적 연결 라이브러리 실행…

0 Comments

  1. 말씀하신 대로 j의 초기값을 i의 제곱으로 두었더니 백만까지 검출하는데 1초도 걸리지 않네요. i의 max 값은 이미 limit로 구현했기 때문에 따로 할 필요까지는 없을 것 같구요.

    알려주셔서 정말 고맙습니다.

  2. 안녕하세요 :$

    위의 코드에서 조금[?] 더 개선할 방법을 끄적이자면,

    ## for(j = i*2; j <= Num; j += i) j의 초기값을 i*i 로 계산해도 올바른 답을 구할 수 있습니다. (주의하셔야 할 점은, i*i 가 integer 값을 넘어서는 경우인데, 이는 i의 max를 sqrt(Num) 까지만 수행되도록 해주면 해결됩니다.) 초기값을 i*i 로 두어도 되는 이유는, ' for j ' 가 하는 역할이 소수 i의 배수를 체크하는 것인데, "i*i 보다 작은 수" 중 "i의 배수"는 이미 체크되어있기 때문이지요.

  3. 에라토스테네스 할부지가 아니었으면 과연 지금도 소수를 계산할 수 있을지… 한 명의 천재가 세상을 바꾼다는 게 맞는 말 같아요. 언젠가는 또 다른 천재가 소수의 일반항 공식을 만들지도요.

  4. 앗.. 저건 그 전설의 소(솟)수 계산해내는 방식….
    이세상에 수열중에서 “소(발음 : 솟)수의 수열”만이
    증명(일반항 구하는 공식)이 안되고 있죠..

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다