프로그램을 작성할 때 소수를 구해야 할 일이 종종 있다. 이 때에는 조건에 따라 두 가지 방법 중 적절한 것을 골라서 사용하게 된다.

2 ~ √N 중 약수 검사하기

이 방법은 N이 소수인지 아닌지를 판별하는 방법으로, 매우 직관적이다. 일반적으로 √N까지의 수 중에 N의 약수가 없으면 그 이후의 수에도 N의 약수가 없다는 것을 가지고 N의 소수 여부를 검사하는 방법이다.

소수 구하기 (Language : c)
  1. #include <stdio.h>
  2. #include <math.h>
  3.  
  4. int N, i;
  5. bool flag;
  6.  
  7. int main()
  8. {
  9.     scanf("%d", &N);
  10.     flag=0;
  11.  
  12.     for(i=2;i<=sqrt((double)N);i++) {
  13.         if(N%i==0) {
  14.             flag=1;
  15.             break;
  16.         }
  17.     }
  18.     if(flag==1 || N==1)
  19.         printf ("%d는 소수가 아닙니다.\n", N);
  20.     else
  21.         printf ("%d는 소수입니다.\n", N);
  22.     return 0;
  23. }

위 프로그램은 입력한 수가 소수인지 아닌지를 알려준다. 2부터 √N까지의 수 중에 N의 약수가 있는지를 검사하는 방법이다.

위 방법은 특정 수의 소수 여부를 판별하기에는 빠르다는 장점을 가지고 있다. 하지만 일정 범위의 모든 수 중 소수를 구할 때 쓰기에는 아래의 방법보다 훨씬 비효율적일 수 있다.


에라토스테네스의 체

이 방법은 에라토스테네스가 고안한 방법으로, 여러 수를 체를 이용해 거르는 모양과 흡사하다고 해서 아리스토테네스의 체라고 불리운다.

2부터 N까지의 수 중 소수를 구한다고 하자. 이 때 K가 소수일 때 K의 배수는 소수가 될 수 없다. 가장 작은 소수는 2이며, 따라서 2의 배수는 소수가 될 수 없다. 이 때 모든 2의 배수에 체크를 시켜둔다. 체크되지 않은 수 중 가장 작은 수는 3이고, 3의 배수에도 모두 체크한다. 이러한 방법으로 N까지 검사하면 2~N 사이에 있는 모든 소수를 빠른 시간 내에 구할 수 있다.

N 이하의 모든 소수 출력 (Language : c)
  1. #include <stdio.h>
  2. #define MAX (10000000)
  3.  
  4. __int64 N, i, j;
  5. bool chk[MAX+1];
  6.  
  7. int main()
  8. {
  9.     scanf("%I64d", &N);
  10.     while(N<2 || N>MAX)
  11.     {
  12.         printf ("다시 입력해 주세요.\n");
  13.         scanf("%I64d", &N);
  14.     }
  15.  
  16.     for(i=2;i<=N;i++)
  17.     {
  18.         if(chk[i]==0)
  19.         {
  20.             for(j=i*2;j<=N;j+=i)
  21.             {
  22.                 chk[j]=1;
  23.             }
  24.             printf ("%I64d\n", i);
  25.         }
  26.     }
  27.    
  28.     return 0;
  29. }

아리스토테네스의 체를 C로 작성해보면 위의 소스와 같다. 2의 배수는 모두 소수가 아니라는 점에 착안하여 메모리 사용과 실행 속도를 반으로 줄이는 방법도 있으나 편의상 그러한 방법은 사용하지 않았다.

위의 방법을 쓰면 상당히 빠르게 소수를 구할 수 있다. 특히 수가 커지면 커질 수록 처음의 방법보다 더 빠른 결과를 보여준다. 다만, 메모리가 많이 쓰이고 임의의 수의 소수 여부를 판단하는 데에는 적절치 못하다.

출력 부분을 제외하고 2부터 N까지의 모든 소수를 구할 때의 수행 속도는 다음과 같다.
N 수행 속도(tick)
10,000 0
100,000 15
1,000,000 282
10,000,000 3265
100,000,000 33813

수행 속도의 단위로 쓰인 tick은 ms와 거의 같은 단위로 봐도 무방하다. (따지자면 차이가 있지만 같다고 간주하자.) 대부분의 프로그램 문제에서는 1초 내에 처리할 것을 요구하므로 1,000,000 정도까지의 소수를 구하는 데에는 무리가 없을 것이다.

다만 주의할 점은 특정 범위(N~M)의 소수를 구하고 싶어도 2부터 M까지 돌려야 한다는 것이다.

문제의 요구에 따라 두 방법 가운데 적절한 방법을 골라 쓸 수 있어야 할 것이다.


2008년 1월 19일 이후 작성된 모든 글에 대해서 퍼가는 것을 금지합니다.
퍼가고자 하시는 분은 링크를 달아 주시기 바랍니다.
Posted by Harry
배낭채우기 문제란, 정해진 용량의 배낭이 있을 때, 이 배낭에 주어진 물건을 넣어서 최대한의 가치를 내는 문제이다. 각 물건은 고유한 무게와 가치가 있으며, 배낭에 들어가는 모든 물건의 무게의 합은 배낭 용량을 초과하지 못한다.

이 문제는 크게 0/1 Knapsack과 Fractional Knapsack으로 나뉜다.
0/1 Knapsack : 물건을 쪼갤 수 없다. 항상 배낭에 들어가지 않거나, 들어가거나 둘 중 하나이다. 동적 계획법으로 풀 수 있다. 이 문제의 경우에는 두 가지 유형이 있는데, 물건의 종류당 개수가 한정되어 있는 것과 무한정인 것 두 가지가 있다.
Fractional Knapsack : 물건을 쪼갤 수 있다. 무게당 가치를 구하여 Greedy를 이용해 풀 수 있다.

일반적으로 Knapsack 문제라고 하면 0/1 Knapsack을 가리키는 경우가 많다.
이 문제를 풀기 위해서는 동적 계획법을 적용해야 한다.

다음은 종류당 물건의 개수가 무한인 문제의 경우의 솔루션이다.

Solution 1제일 먼저, 물건의 종류를 늘려가면서 생각한다. 가장 처음에는 물건이 한 종류만 있다고 가정한다. 이럴 경우, 가방의 무게를 증가시키면서 각 경우의 최적해를 구한다.
다음에는 두 번째 물건이 있다고 생각하고, 이 물건을 배낭에 넣을 때의 경우와 원래 구해 놓은 최적해를 비교하여 더 높은 가치를 내는 것을 선택한다.
위의 과정을 반복하면 된다.

Dy[i] : 가방의 용량이 i일 때의 최적해
공간복잡도 : M(가방의 용량), 시간복잡도 : NM(물건의 개수 * 가방의 용량)


다음은 종류당 물건의 개수가 각 1개로 한정되어 있는 경우의 솔루션이다.

Solution 2제일 먼저, 가방의 용량을 늘려가면서 각 용량당 물건의 개수를 늘려가며 생각한다.

Dy[i][j] : 가방의 용량이 i이고 j번째 물건까지만 있을 때의 최적해(얻을 수 있는 최대가치)
Dy[i][j]=MAX(Dy[1 ~ (i-W[j])][j-1]+W[j], Dy[1 ~ i][j-1])
공간복잡도 : MN(가방의 용량 * 물건의 개수), 시간복잡도 : M2N(가방의 용량2 * 물건의 개수)

Modified at 5/15 11:37 개수 한정의 경우 솔루션 추가



2008년 1월 19일 이후 작성된 모든 글에 대해서 퍼가는 것을 금지합니다.
퍼가고자 하시는 분은 링크를 달아 주시기 바랍니다.
Posted by Harry

위상정렬이란?


위상정렬은 일의 순서를 정하는 것이라고 요약할 수 있다. 스타크래프트를 생각하면 쉬워진다. 스타크래프트에서, 마린을 생산하기 위해서는 배럭스가 필요하고, 배럭스를 짓기 위해서는 먼저 SCV가 있어야 한다. 즉 마린을 배럭스보다 먼저 생산할 수는 없으며, SCV 없이 배럭스를 지을 수 없다는 것이다. 이와 같이 어떤 일을 하기 위해서 꼭 선행되어야 하는 일이 정해져있을 때, 모든 일을 하기 위한 일의 순서를 정하는 것이 위상정렬이다.

우선순위 큐란?


우선순위 큐는 힙을 이용하는 자료구조이다. 힙이란, 자료의 삽입/삭제시에 모든 자료의 정렬이 자동으로 이루어지는 구조이다. 힙은 기본적으로 이진 트리의 구조인데, Parent, Left, Right가 있을 때 항상 Parent가 제일 크다는 전제 하에 이루어지는 트리이다. STL에서는 우선순위 큐를 제공하므로 이를 사용하면 편리하게 프로그래밍을 할 수 있다.
위상정렬을 할 때 원래는 그냥 큐를 사용해도 무방하나, 각 일에 번호가 매겨져있고, 가능하다면 작은 번호가 선행되도록 하고 싶다면 우선순위 큐를 사용하여야 한다. 우선순위 큐는 한 번의 삽입, 삭제시에 lgN의 시간이 소요되기 때문에 큰 부담 없이 사용할 수 있다.

우선순위 큐를 이용한 위상정렬의 방법

1. 모든 일의 선행 필요조건 관계를 따라 그래프를 그린다. (W1을 해야 W2를 할 수 있는 경우 W1에서 W2로 간선을 그린다. 이와 같이 하면 방향성 그래프가 완성된다.)
2. 각 일에 대해 Indegree를 센다. (Indegree = 내차수 = 자신으로 들어오는 간선의 개수)
3. Indegree가 0인 일을 모두 찾아 우선순위 큐에 넣는다.
4. 우선순위 큐에서 일 하나를 꺼낸다.(위상정렬의 결과를 출력하고 싶다면 이 때 꺼내진 일을 출력하면 된다.)
5. 방금 꺼낸 일에 연결된 모든 일들의 Indegree를 1씩 감소시킨다.
6. Indegree가 감소된 일들 중 Indegree가 0인 일이 있으면 우선순위 큐에 넣어준다.
7. 우선순위 큐가 빌 때까지 4~6을 반복한다.

관련 문제 보기 - ACM UVA 10305번





2008년 1월 19일 이후 작성된 모든 글에 대해서 퍼가는 것을 금지합니다.
퍼가고자 하시는 분은 링크를 달아 주시기 바랍니다.
Posted by Harry
그라함 스캔은 볼록다각형을 구하는 가장 일반적인 방법이다.

  1. 외곽의 점을 하나 잡는다. 이는 반드시 최외곽에 존재해야 하며, 주로 y가 가장 작은 점 등을 선택한다.
  2. 다른 점들을 선택된 점(pivot이라 합니다)을 기준으로 각도가 증가하는 순서로 정렬한다. 이를 다 선으로 이으면 별 모양이 만들어진다.
  3. 다각형을 만들어나간다. 각 점들에 대해 다른 점이 해당 선분의 좌측인지 우측인지를 판단하면서 진행한다.


그라함 스캔 시뮬레이션
http://www.cs.princeton.edu/~ah/alg_anim/version1/GrahamScan.html




2008년 1월 19일 이후 작성된 모든 글에 대해서 퍼가는 것을 금지합니다.
퍼가고자 하시는 분은 링크를 달아 주시기 바랍니다.
Posted by Harry