了解凸包及Graham掃描法
問題描述:二位平面內,給定n個散亂的點,求一個最小凸多邊形(凸包),使得n個點都不在凸多邊形外。
問題的解決用到Graham算法:
算法步驟:
1.取y坐標最小的一點,作為p0,顯然p0一定在凸包上。
2.將p0作為坐標系原點,其他點按極角從小到大排序,從p1開始編號。
3.從小到大遍歷所有點:顯然[p0, p1] 在凸包中
4.連接p1, p2的時候需要判斷:p0->p1 叉乘 p1->p2 是否大於0:
> 0 p1->p2 在 p0->p1 夾角小於π,物理意義:p1->p2 在 p0->p1的左邊,滿足凸多邊形定義。
= 0 p1->p2 與 p0->p1 共線,同向滿足,相反不滿足。
< 0 p1->p2 在 p0->p1 夾角大於π,不滿足。
5.連接p2, p3 向量p2->p3在p1->p2左邊,滿足定義,當連接p3, p4的時候,發現不滿足定義了,此時要放棄p3, 從p2開始回溯,找到第一個滿足要求的點。
6.以此類推,知道回到p0點。
Graham scan 正確性:
令散點的數量為k,散點(p0 ~ pk – 1)已經按照極角排序。
當k=3時,顯然,p0->p1時凸包的一條邊,且p2 極角小於p1, 那么p1->p2在p0->p1的左側,所以p1->p2保留。
當k=n時,假設此時(p0 ~ pn – 1) 都按照Graham scan找出“最完美的凸包”
當k=n+1時,如果pn – 1 -> pn 在 pn – 2 > pn – 1左邊,如下圖,如果舍棄pn,直接連接pn – 1 -> p0, 那么pn在多邊形外,不滿足要求。
證畢。
代碼實現:
/****************************凸包模板*******************************/ const double eps = 1e-8; int sgn(double x) { if (fabs(x) < eps) return 0; if (x < 0) return -1; else return 1; } struct Point { double x, y; Point() {} Point(double _x, double _y) { x = _x; y = _y; } Point operator-(const Point &b) const { return Point(x - b.x, y - b.y); } //叉積 double operator^(const Point &b) const { return x * b.y - y * b.x; } //點積 double operator*(const Point &b) const { return x * b.x + y * b.y; } void input() { scanf("%lf%lf", &x, &y); } }; struct Line { Point s, e; Line() {} Line(Point _s, Point _e) { s = _s; e = _e; } }; //*兩點間距離 double dist(Point a, Point b) { return sqrt((a - b) * (a - b)); } /* * 求凸包,Graham算法 * 點的編號0~n-1 * 返回凸包結果Stack[0~top-1]為凸包的編號 */ const int MAXN = 1010; Point List[MAXN]; int Stack[MAXN]; //用來存放凸包的點 int top; //表示凸包中點的個數 //相對於List[0]的極角排序 bool _cmp(Point p1, Point p2) { double tmp = (p1 - List[0]) ^(p2 - List[0]); if (sgn(tmp) > 0) return true; else if (sgn(tmp) == 0 && sgn(dist(p1, List[0]) - dist(p2, List[0])) <= 0) return true; else return false; } void Graham(int n) { Point p0; int k = 0; p0 = List[0]; //找最下邊的一個點 for (int i = 1; i < n; i++) { if ((p0.y > List[i].y) || (p0.y == List[i].y && p0.x > List[i].x)) { p0 = List[i]; k = i; } } swap(List[k], List[0]); sort(List + 1, List + n, _cmp); if (n == 1) { top = 1; Stack[0] = 0; return; } if (n == 2) { top = 2; Stack[0] = 0; Stack[1] = 1; return; } Stack[0] = 0; Stack[1] = 1; top = 2; for (int i = 2; i < n; i++) { while (top > 1 && sgn((List[Stack[top - 1]] - List[Stack[top - 2]]) ^ (List[i] - List[Stack[top - 2]])) <= 0) top--; Stack[top++] = i; } } /****************************凸包模板*******************************/