Graham Scan

Computational Geometry 2021. 3. 28. 10:35

Convex hull을 찾을 때, 중복점과 일직선상 위에 있는 점들에 대해서 적절한 처리를 하지 않으면 효율적으로 작성된 알고리즘이라도 정상적으로 동작하지 않을 수 있다. 그림은 Graham Scan 알고리즘을 이용해서 50,000개 점들의 convex hull을 구한 결과다. 각 정렬을 하기 위해서는 pivort point가 필요한데 std::sort()을 호출할 때 compare()에서 global 변수를 쓰지 않기 위해서 첫 원소를 pivot point로 잡아서 사용한다. 정렬 후에는 빼주었던 pivot를 다시 더해준다.

inline int LeftSide(const CPoint &A, const CPoint &B, const CPoint &C) {
    return (B.x - A.x) * (C.y - A.y) - (C.x - A.x) * (B.y - A.y); // cross(AB, AC);
}
static int cross(const CPoint &p1, const CPoint &p2) {
    return p1.x * p2.y - p1.y * p2.x;
}
static int norm2(const CPoint &p) {
    return p.x * p.x + p.y * p.y;
}
static int compare(const CPoint &a, const CPoint &b) {
    int ccw = cross(b, a);
    // 거리가 먼 곳 --> 가까운 곳;
    if (ccw == 0) return norm2(b) < norm2(a);
    return ccw < 0; // 반시계방향 정렬;
}
static void angularSort(std::vector<CPoint> & pts) {
    if ((pts.size()) < 2) return;
    int id = 0;
    for (int i = pts.size(); i-->0;) 
        if ((pts[id].y > pts[i].y) || (pts[id].y == pts[i].y && pts[i].x > pts[id].x)) 
            id = i;  //lowest--y-value; //largest--x-value;
    std::swap(pts[0], pts[id]);
    for (int i = pts.size(); i-->1;) pts[i] -= pts[0];
    std::sort(pts.begin()+1, pts.end(), compare);
    for (int i = pts.size(); i-->1;) pts[i] += pts[0];
};
// AB와 BC가 나란할 때, AB 방향과 BC방향이 같은가(0) 반대면 (1): dot(AB, BC);
static int folded(const CPoint &A, const CPoint &B, const CPoint &C) {
    return (B.x - A.x) * (C.x - B.x) + (B.y - A.y) * (C.y - B.y) <= 0;
}
std::vector<CPoint> GrahamScan(const std::vector<CPoint>& points) {
    if (points.size() < 3) return std::vector<CPoint> (); //null_vector;
    std::vector<CPoint> pts = points; //clone;
    angularSort(pts);
    std::vector<CPoint> hull(pts.size());
    hull[0] = pts[0];
    int i = 1; 
    while (i < pts.size()) { 
        // peek out points degenerate with pts[0];
        if (pts[i] != hull[0]) {
            hull[1] = pts[i++]; break;  //  (i++)
        }
        i++;
    }
    if (i == pts.size()) return std::vector<CPoint> ();//null_vector;
    int k = 2; // 새로 추가되는 hull index;
    while (i < pts.size()) {
        // (i)가 hull의 left에 있으면(1) hull의 꼭지점에 추가;
        int ccw = LeftSide(hull[k - 2], hull[k - 1], pts[i]); 
        if (ccw > 0) hull[k++] = pts[i++];
        // (i)가 hull의 직전 변과 collinear한 데 반대방향을 향하면 skip;
        else if (ccw == 0 && folded(hull[k - 2], hull[k - 1], pts[i])) ++i;
        // (i)가 hull의 right 또는 위에 있으면(직전 변과 같은 방향)
        // hull의 직전 꼭지점을 순차적으로 제거한 후 다시 검사;
        else --k;
        ASSERT(k >= 2);
    }
    hull.resize(k);
    return hull;
};
728x90

'Computational Geometry' 카테고리의 다른 글

Rotating Calipers  (3) 2021.03.31
Convex Hull Peeling  (0) 2021.03.29
Jarvis March  (0) 2021.03.26
Approximate Minimum Enclosing Circle  (1) 2021.03.18
Minimum Bounding Rectangle  (3) 2021.03.15
Posted by helloktk
,