计算几何--二维几何常用算法(多边形和凸包)
内容参考书籍——《算法竞赛入门经典训练指南》
在程序中,用顶点数组表示多边形,其中各个顶点按照逆时针顺序排列。
判断点是否在多边形内。采用转角法,基本思想是计算多边形相对于判定点转了多少度,具体来说,将多边形每条边的转角加起来,如果是360°,说明在多边形内;如果是0°,说明在多边形如果是180°则在多边形边界上。该方法在处理一些弧形多边形时丝毫不受影响,只需要每一段的终点到起点的转角累加起来即可。另外这个三角形甚至可以不是简单多边形(即可以自交)。
然而,直接计算会使用大量的反三角函数,不仅速度慢且容易产生精度误差。在算法竞赛中,我们并不会这样做,而是假想有一条向右的射线,统计多边形穿过这条射线正反多少次,把这个数记为绕数wn(Winding Number),逆时针穿过时,wn加1,顺时针穿过时,wn减1。
注意程序实现时,判断是否穿过,以及穿过方向时,需要用叉积判断输入点在边的左边还是右边。
点在凸多边形内的判定更简单,只需要判断是否在所有边的左边(假设各顶点按照逆时针顺序排序)即可
凸包。
凸包就是把定点包围在内部的、面积最小的凸多边形。基于水平序的Andrew算法(比原始的Graham更快且更稳定)。首先把所有点按x从小到大排序(如果x相同,按照y从小到大排序),删除重复点后得到序列p1,p2,...,然后把p1和p2放到凸包中。从p3开始,当新点在凸包“前进”方向的左边时继续,否则依次删除最近加入凸包的点,直到新点在左边。
如下图所示,新点P18在向量P10P15(当前“前进方向“)的右边,因此需要从凸包上删除P15和P10,让P8的下一个点为P18。重复这个过程,直到碰到最右边的Pn,就求出了“下凸包”。然后反过来从Pn开始再做一次,求出“上凸包”,合并起来就是完整的凸包。
这个算法在排序后仅仅是从左到右和从右到左各扫描了一次,时间复杂度为O(n)。加上排序后时间复杂度也仅为O(nlogn)。
代码如下:
1 //判断该点与多边形关系 2 int isPointInPolygon(Point p, Polygon poly) 3 { 4 int wn = 0; 5 int n = v.size(); 6 for (int i = 0; i < n; ++i) 7 { 8 if (isPointOnSegment(p,poly[i], poly[(i+1)%n])) return -1; 9 int k = dcmp(Cross(poly[(i+1)%n]-poly[i]),p-poly[i]); 10 int d1 = dcmp(poly[i].y-p.y); 11 int d2 = dcmp(poly[(i+1)%n].y-p.y); 12 if (k>0 && d1<=0 && d2>0) wn++; 13 if (k<0 && d2<=0 && d1>0) wn--; 14 } 15 if (wn != 0)return 1; 16 return 0; 17 } 18 //计算凸包,输入点数组p,个数为p,输出点数组ch。函数返回凸包顶点数。 19 //输入不能有重复点。函数执行完之后输入点的顺序被破坏。 20 //如果不希望在凸包的边上有输入点,把两个<=改成< 21 //在精度要求高时建议用dcmp比较 22 int ConvexHull(Point* p, int n, Point* ch) 23 { 24 sort(p,p+n); 25 int m = 0; 26 for (int i = 0; i < n; ++i) 27 { 28 while(m>1 && Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2]) <= 0) m--; 29 ch[m++] = p[i]; 30 } 31 int k = m; 32 for (int i = n-2; i >= 0; --i) 33 { 34 while(m >k && Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2]) <= 0) m--; 35 ch[m++] = p[i]; 36 } 37 if (n > 1) m--; 38 return m; 39 }
求多边形的重心
hdu 1115:给一个n多边形,求重心。
1 #include <bits/stdc++.h> 2 using namespace std; 3 struct Point 4 { 5 double x,y; 6 Point(double X = 0,double Y = 0){x=X,y=Y;} 7 Point operator + (Point B) {return Point (x+B.x,y+B.y);} 8 Point operator - (Point B) {return Point (x-B.x,y-B.y);} 9 Point operator * (double k) {return Point (x*k,y*k);} 10 Point operator / (double k) {return Point (x/k,y/k);} 11 }; 12 typedef Point Vector; 13 double Cross(Vector A,Vector B){return A.x*B.y-A.y*B.x;} 14 double Polygon_area(Point *p,int n) //求多边形有向面积,有正负不能取绝对值 15 { 16 double area = 0; 17 for (int i = 0; i < n; ++i) 18 { 19 area += Cross(p[i],p[(i+1)%n]); 20 } 21 return area/2; 22 } 23 Point Polygon_center(Point *p,int n) //求重心 24 { 25 Point ans(0,0); 26 if (Polygon_area(p,n) == 0) return ans; 27 for (int i = 0; i < n; ++i) 28 { 29 ans = ans+(p[i]+p[(i+1)%n])*Cross(p[i],p[(i+1)%n]); 30 } 31 return ans/Polygon_area(p,n)/6; 32 } 33 int main(int argc, char const *argv[]) 34 { 35 int t,n,i; 36 Point center; 37 Point p[100000]; 38 scanf("%d",&t); 39 while(t--) 40 { 41 scanf("%d",&n); 42 for (int i = 0; i < n; ++i) 43 { 44 scanf("%lf %lf",&p[i].x,&p[i].y); 45 } 46 center = Polygon_center(p,n); 47 printf("%.2f %.2f\n",center.x,center.y); 48 } 49 return 0; 50 }
hdu 1392:输入n个点,求凸包的周长。
代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int maxn = 104; 4 const double eps = 1e-8; 5 int sgn(double x) //判断x是否为0 6 { 7 if (fabs(x)<eps) return 0; 8 else return x<0? -1:1; 9 } 10 struct Point 11 { 12 double x,y; 13 Point(){} 14 Point(double x, double y):x(x),y(y){} 15 Point operator + (Point B) {return Point (x+B.x,y+B.y);} 16 Point operator - (Point B) {return Point (x-B.x,y-B.y);} 17 bool operator == (Point B) {return sgn(x-B.x) == 0 && sgn(y-B.y) == 0;} 18 bool operator < (Point B) //用于sort()排序 19 { 20 return sgn(x-B.x)<0 || (sgn(x-B.x) == 0 && sgn(y-B.y)<0); 21 } 22 }; 23 typedef Point Vector; 24 double Cross(Vector A,Vector B){return A.x*B.y-A.y*B.x;}//叉积 25 double Distance(Point A, Point B){return hypot(A.x-B.x,A.y-B.y);} 26 //求凸包。凸包顶点放在ch中,返回值是凸包的顶点数 27 int ConvexHull(Point* p, int n, Point* ch) 28 { 29 sort(p,p+n); //对点排序:按从大到小排序,如果x相同,按y排序 30 n=unique(p,p+n)-p; //去重复点 31 int m = 0; 32 //求下凸包,如果p[i]是右拐弯的,这个点不在凸包上,往回退 33 for (int i = 0; i < n; ++i) 34 { 35 while(m>1 && Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2]) <= 0) m--; 36 ch[m++] = p[i]; 37 } 38 int k = m; //求上凸包 39 for (int i = n-2; i >= 0; --i) 40 { 41 while(m >k && Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2]) <= 0) m--; 42 ch[m++] = p[i]; 43 } 44 if (n > 1) m--; 45 return m; //返回凸包顶点数 46 } 47 int main(int argc, char const *argv[]) 48 { 49 int n; 50 Point p[maxn],ch[maxn]; 51 while(scanf("%d",&n) && n) 52 { 53 for (int i = 0; i < n; ++i) 54 { 55 scanf("%lf%lf",&p[i].x,&p[i].y); 56 } 57 int v = ConvexHull(p,n,ch);//凸包顶点数 58 double ans = 0; 59 if (v==1) ans=0; 60 else if(v==2)ans=Distance(ch[0],ch[1]); 61 else 62 { 63 for (int i = 0; i < v; ++i) //计算凸包周长 64 { 65 ans+=Distance(ch[i],ch[(i+1)%v]); 66 } 67 } 68 printf("%.2f\n",ans); 69 } 70 return 0; 71 }