Processing math: 100%

Andrew 算法(构造凸包)

简要#

这是一个基 \(Graham\) 思想的又一个算法,我个人认为其中复杂度的改进应该是在\(sort\)\(cmp\) 部分吧,
\(Graham\)\(cmp\) 部分需要对两个点算出叉乘,甚至在某些情况还要算出两个点的距离,这一步骤应该相对而言复杂度是较高的。
\(Andrew\) 很好的避免了这一步骤,直接通过对点的 \(x, y\) 的简单排序就可以实现一个 \(nlogn\) 的算法

算法思想#

  • 首先我们要对所有的点进行排序,通常情况下是按照,\(X\) 从小到大,如果 \(X\) 相等的话,按照 \(Y\) 从小到大。
    这里有一点我们必须明白,排序后,最前面和最后面的点一定是极点。

  • 接着就是进行 \(Graham\) 算法的重要的一步 \(Scan\)
    第一遍 \(Scan\) 我们从最左端的点出发,做一遍 \(Scan\) 我们可以得到整个凸包的下部分。
    第二遍 \(Scan\) 我们从最右端的点出发,做一遍 \(Scan\) 我们可以得到整个凸包的上部分。
    这两份 \(Scan\) 合并起来就是完整的凸包了。

实现图例#

这是第一遍 \(Scan\),蓝色的代表曾经走过,但是因为与后面的点构成凸包矛盾而进行过回溯。红色的线代表这一趟扫描满足条件的级边,也就是构成凸包的下半部分的边。

这是第二遍 \(Scan\),绿色的代表曾经走过,但是因为与后面的点构成凸包矛盾而进行过回溯。黑色的线代表这一趟扫描满足条件的级边,也就是构成凸包的上半部分的边。

时间复杂度分析#

\(sort\) 的时间是 \(O(nlogn)\) 两趟 \(scan\) 的时间都是线性的,整地复杂度是 \(O(nlogn)\),但是我们认为这个算法的时间复杂度是优于 \(graham\) 的,原因应该是我在开头提到的把

模板题#

P2742 [USACO5.1]圈奶牛Fencing the Cows /【模板】二维凸包

学习过程中\(debug\)很乱的代码#

Copy
/* Code by lifehappy 2020:04:17 凸包Andrew算法 */ #include<bits/stdc++.h> using namespace std; const double INF = 1e100; const double eps = 1e-8; const double pi = acos(-1.0); const int N = 1e5 + 10; int n, cnt, m; int sgn(double x) { if(fabs(x) < eps) return 0; if(x > 0) return 1; return -1; } struct point { double x, y; point(double a = 0.0, double b = 0.0) : x(a), y(b) {} bool operator < (point t) { if(sgn(x - t.x) == 0) return y < t.y; return x < t.x; } }p[N], ans[N], all[N]; point operator - (point a, point b) { return point(a.x - b.x, a.y - b.y); } double dis(point a, point b) { a = a - b; return sqrt(a.x * a.x + a.y * a.y); } double cross(point a, point b) { return a.x * b.y - a.y * b.x; } void Andrew() { sort(p, p + n); int p1 = 0, p2; for(int i = 0; i < n; i++) { while(p1 > 1 && sgn(cross(ans[p1 - 1] - ans[p1 - 2], p[i] - ans[p1 - 2])) == -1) p1--; ans[p1++] = p[i]; } // cout << p1 << endl; // for(int i = 0; i < p1; i++) { // int flag = 1; // for(int j = 0; j < m; j++) // if(sgn(ans[i].x - all[j].x) == 0 && sgn(ans[i].y - all[j].y) == 0) { // flag = 0; // break; // } // printf("%lf %lf %s\n", ans[i].x, ans[i].y, flag ? "False" : " True"); // } // cout << p1 << endl; p2 = p1; for(int i = n - 2; i>= 0; i--) { while(p2 > p1 && sgn(cross(ans[p2 - 1] - ans[p2 - 2], p[i] - ans[p2 - 2])) == -1) p2--; ans[p2++] = p[i]; // cout << p2 << endl; } // for(int i = 0; i < p2; i++) // printf("%lf %lf\n", ans[i].x, ans[i].y); // p2--; p2--; // for(int i = p1; i < p2; i++) { // int flag = 1; // for(int j = 0; j < m; j++) // if(sgn(ans[i].x - all[j].x) == 0 && sgn(ans[i].y - all[j].y) == 0) { // flag = 0; // break; // } // printf("%lf %lf %s\n", ans[i].x, ans[i].y, flag ? "False" : " True"); // } // cout << p2 << endl; double target = 0.0; for(int i = 0; i < p2; i++) target += dis(ans[i], ans[i + 1]); printf("%.2f\n", target); } int main() { // freopen("in.txt", "r", stdin); // freopen("out.txt", "w", stdout); // scanf("%d", &m); // for(int i = 0; i < m; i++) // scanf("%lf %lf", &all[i].x, &all[i].y); scanf("%d", &n); for(int i = 0; i < n; i++) scanf("%lf %lf", &p[i].x, &p[i].y); Andrew(); // point a(-9934.480000, -2886.200000), b(-9595.260000, -1905.480000), c(-9124.100000, -4804.250000); // printf("%lf\n", cross(b - a, c - a)); return 0; }

较为简洁的代码#

Copy
/* Code by lifehappy 2020:04:17 凸包Andrew算法 */ #include<bits/stdc++.h> using namespace std; const double INF = 1e100; const double eps = 1e-8; const double pi = acos(-1.0); const int N = 1e5 + 10; int n, cnt, m; int sgn(double x) { if(fabs(x) < eps) return 0; if(x > 0) return 1; return -1; } struct point { double x, y; point(double a = 0.0, double b = 0.0) : x(a), y(b) {} bool operator < (point t) { if(sgn(x - t.x) == 0) return y < t.y; return x < t.x; } }p[N], ans[N], all[N]; point operator - (point a, point b) { return point(a.x - b.x, a.y - b.y); } double dis(point a, point b) { a = a - b; return sqrt(a.x * a.x + a.y * a.y); } double cross(point a, point b) { return a.x * b.y - a.y * b.x; } void Andrew() { sort(p, p + n); int p1 = 0, p2; for(int i = 0; i < n; i++) { while(p1 > 1 && sgn(cross(ans[p1 - 1] - ans[p1 - 2], p[i] - ans[p1 - 2])) == -1) p1--; ans[p1++] = p[i]; } p2 = p1; for(int i = n - 2; i>= 0; i--) { while(p2 > p1 && sgn(cross(ans[p2 - 1] - ans[p2 - 2], p[i] - ans[p2 - 2])) == -1) p2--; ans[p2++] = p[i]; } p2--; double target = 0.0; for(int i = 0; i < p2; i++) target += dis(ans[i], ans[i + 1]); printf("%.2f\n", target); } int main() { // freopen("in.txt", "r", stdin); // freopen("out.txt", "w", stdout); scanf("%d", &n); for(int i = 0; i < n; i++) scanf("%lf %lf", &p[i].x, &p[i].y); Andrew(); return 0; }
posted @   lifehappy  阅读(835)  评论(0编辑  收藏  举报
编辑推荐:
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
阅读排行:
· 为DeepSeek添加本地知识库
· 精选4款基于.NET开源、功能强大的通讯调试工具
· DeepSeek智能编程
· [翻译] 为什么 Tracebit 用 C# 开发
· 腾讯ima接入deepseek-r1,借用别人脑子用用成真了~
点击右上角即可分享
微信分享提示
CONTENTS