凸包学习小结
这两天翻看了些许凸包的资料,在此留下学习脚印……
凸包是啥呢……问度娘 = =、
大家都知道的就不说了,很多资料都写了,而且目测写得也比我好多了吧……
常用算法:枚举法 O(n ^ 3)的复杂度 , Graham扫描法 , 分治法(不懂....)
让我兴奋的是,另一种网上资料难以找到的Graham扫描算法的变种:(LRJ训练指南上称作:Andrew算法)
LRJ注释上写此种算法更快,且数值稳定性更好……故很想一试,奈何神牛就是神牛,他的介绍文字加起来不过1页……让我这个原来连凸包是什么都不懂的菜鸟鸭梨山大啊……
后来,在捉题目的时候,翻看某博主的文章,里面写的与Graham算法完全不同,因为他不用将点按极角(幅角)排序,而是直接对点按y坐标的升序排列,让我眼前一亮。
说真的,Graham算法在判断平角时还是比较麻烦的……而Andrew算法正好回避了这个问题…
果断照着打了一遍,在通过白书和自己模拟后,也明白了些许:
算法流程:
1、将点按y坐标升序排列(y相同的按x升序)
2、将点p[0] , p[1]入栈,指针top指向1
3、判断三角区符号,如果为负,说明当前栈针所指点不满足凸包上的点,则将点退栈
4、(3)过程所得是“上凸包”,我们再反过来从p[n]开始,做一次,得到“下凸包”
5、两次所得(此时栈中的点)就是完整的凸包了
补充三角区符号知识:
至于这个算法的几点思考:
1、为什么三角区函数为负,则说明前点不属于凸包?
理解:因为新入的点不能在原来两点所成向量的外侧(凸包是要将所有点都包含在里面),因此必须要满足,不满足说明当前点不能属于凸包上的点,需退栈!
2、这个算法怎么处理平角(也就是三角区函数为0)的情况?
解答:这种叉积形式已经包含了这个情况!
------------------------------分割线---------------------------------------
最后附上例题一枚:HDU 1392 Surround the Trees
附上小人的代码,大神勿喷啊……
#include <cstdio> #include <cmath> #include <algorithm> struct point { int x , y; }p[110] , chn[110]; bool cmp(point a , point b) { return (a.y < b.y || (a.y == b.y && a.x < b.x)); } bool xmult (point p1 , point p2 , point p3) { return ((p2.x - p1.x) * (p3.y - p1.y) > (p2.y - p1.y) * (p3.x - p1.x)); } int andrew(int n) { int len , top = 1; chn[0] = p[0]; chn[1] = p[1]; for (int i = 2 ; i < n ; i ++) { while (top && !xmult(chn[top] , chn[top - 1] , p[i])) top --; chn[++ top] = p[i]; } len = top; chn[++ top] = p[n - 2]; for (int i = n - 3 ; i >= 0 ; i --) { while (top != len && !xmult(chn[top] , chn[top - 1] , p[i])) top --; chn[++ top] = p[i]; } return top; } double dis(point a , point b) { return (sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y))); } int main() { int n; while(scanf("%d" , &n) , n != 0) { for (int i = 0 ; i < n ; i ++) scanf("%d%d" , &p[i].x , &p[i].y); std::sort(p , p + n , cmp); int top = andrew(n); double ans = 0.0; for (int i = 0 ; i < top ; i ++) ans += dis(chn[i % top] , chn[(i + 1) % top]); if (top == 2) ans /= 2; printf("%.2lf\n" , ans); } return 0; }