점들의 모임에서 convex hull을 찾는 것은 점들을 잇는 선분자체가 포함되는 다각형을 찾는 것을 의미한다.
간단하게 생각할 수 있는 방법은 최외각의 한점을 기준으로해서(점들을 감싸는 최소 사각형상의 한점) 그 자신을 뺀 모든 점들이 왼편이나 일직선상에 놓일수 있는 점을 찾는다.
다시 이 찾은 점을 기준으로 나머지점들에 대해서 동일한 조건을 만족하는 점들을 차례로 찾으면, 주어진 점들을 감싸는 최소의 점들의 모임을 구할 수 있고, 이것이 convex hull을 구성한다(Gift Wrap).
이 알고리즘은 n개의 점들에 대해서 각각 O(n)정도의 찾기를 시도하므로 전체적으로 O(n^2)정도의 스텝이 필요한 알고리즘이다.
여기서 찾는 과정을 좀더 빠르게 할 수 있다면 전반적인 알고리즘의 복잡도도 감소한다.
따라서 점들은 우선 일정한 순서대로 정열을 하고난 후에 순차적으로 들어오는 점들이 왼편에 있는가만 판별하면 되는 과정으로 줄일 수 있다.
이제 conex hull을 구하는 알고리즘중에서 잘알려진 Graham Scan 알고리즘을 살펴보도록 하자.
이 알고리즘은 앞에서 설명한 과정을 그대로 따라 진행이 되는데, 우선 점들을 맨 바깥쪽에 있는 한점
(점들을 감싸는 최소 사각형상의 임의의 한점이면 충분하다. 보통은 최저의 y값을 갖는 점들중에 최대의 x값을 갖는 점으로 기준점으로 잡는다)을
기준으로 해서 각도의 크기로 점들을 정열을 해 둔상태에서 진행이 된다.
정열방법은 기준점을 각도를 계산하여 정열을 할 수 있으나, 각도수치계산의 부정확성과 복잡한 삼각함수 연산에 의존하는 것보다는 기하학적인 배치를 고려하면 된다.
i=2인점이 기준점(i=0)과 i=1인 점이 이루는 직선의 오른쪽에 놓이면 i=1과 바꾸는 식으로 하면 정열이 쉽게된다
(두 점이 기준점을 지나는 일직선상에 놓인 경우에는 기준점에서 먼 점만 넣으면 된다.
반시계방향(counterclockwise)으로 정열된 주어진 점들의 집합을 {P[i], i=0, 1, 2, ..., N-1} 이라고 할 때, Graham Scan 알고리즘의 pseudo code는
Push P[0] and P[1] onto a stack; i=2; while(i<N){ Let PT1=the top point on stack ; Let PT2=the second top point on stack ; if (P[i] is strictly left of the line PT2 to PT1) { Push P[i] onto stack ; i++; //increment (i); }else{ Pop the top point PT1 off the stack ; } }
/*c-source*/
//각도를 기준으로 CCW로 정열하는 루틴으로 배열의 처음점은 y-값이 가장 작아야하며, //x값은 가장 큰것으로 swap해야 한다. int angular_sort(POINT P[], int N, POINT Q[]) { //P[0]==lowest and rightmost point; //Q[] = 정렬된 점들을 담는 배열로, 크기는 N이어야 한다. int i, j, k; Q[0]=P[0]; Q[1]=P[1]; k=2; for(i=2; i<N; i++){ int ccw=LEFTSIDE(Q[0],Q[k-1],P[i]); if(ccw>0){//LEFT ; Q[k]=P[i];k++; //push; }else if(ccw==0) {//on the line; //일직선상에 있는 경우에는 가까운 점은 제거하고, 먼점만 선택; if(DIST2(Q[0],P[i])>DIST2(Q[0],Q[k-1])){ Q[k-1]=P[i]; } //만약에 일직선상에 있는 점들을 모두 보전하고자 하면, Q[0]에서 거리 순서대로 //정렬을 하고, Scan과정에서 ccw==0인 경우를 넣어야 한다. }else if(ccw<0){ for(j=k-2; j>0; j--){ ccw=LEFT(Q[0],Q[j], P[i]); if (ccw>0) {//LEFT INSERT(Q, j+1, P[i]); //Q(j+1)에 P[i]를 넣고,상위 Q는 이동, k++; break; }else if(ccw==0){ //ON THE LINE; 먼점만 선택 if(DIST2(Q[0],P[i])>DIST2(Q[0],Q[j])){ Q[j]=P[i]; break; } } } if(j==0 && ccw<0){ INSERT(Q,1,P[i]);//P[i]을 Q의 1번째에 넣고,상위Q는 이동;/ k++; } } } return (k); //일직선상에 있는 점들을 제거하였으므로 k<=N; } int graham_scan(POINT P[], int N, POINT Stack[]) { //P[]는 반시계방향으로 정렬이 된 상태의 배열이어야하고, statck의 크기는 N만큼 되어야 한다. int i=0, k=0; Stack[0]=P[0]; Stack[1]=P[1]; i=k=2; while(i<N) { POINT PT1=Stack[k-1]; //최상위 ; POINT PT2=Stack[k-2]; //차상위; if ( LEFTSIDE(PT2,PT1,P[i]) > 0 ) { Stack[k]=P[i]; k++ ; //push P[i]; i++ ; }else{ k--; //pop PT1; } } return (k); //number of convex hull points; }
첨부한 화일은 위에서 설명한 알고리즘의 과정을 MFC를 써서 구현하였습니다.(첨부한 소스를 사용시 반드시 출처를 남겨주기바랍니다.)
댓글
댓글 쓰기