一、点集有序化-水平排序
在计算几何中,点集往往无序,因此在计算前需要对点集进行排序,使得算法可以有序高效运行。
水平排序利用点在二维平面上固有的横纵坐标属性进行排序,只涉及点坐标的比较,与极坐标排序使用的三角函数相比没有精度问题。
水平排序利用的规则为,纵坐标为第一关键字,横坐标为第二关键字,纵坐标升序排列,如果纵坐标相等,按横坐标升序排列。
1 bool com(const datatype& x,const datatype& y){ 2 if(x.y<y.y) 3 return true; 4 if(x.y>y.y) 5 return false; 6 if(x.y==y.y) 7 return x.x<y.x; 8 }
二、Graham计算凸包
点集有序化后,Graham按逆时针顺序遍历点集,利用栈不断更新迭代凸包结果。
遍历的顺序是,在水平排序后将会出现一个最低点和一个最高点。将最低点加入队列,先从最低点出发遍历,如果在最低点和最高点连线的右侧,就将该点加入访问队列,否则不加入。将最高点加入队列,再从最高点出发,如果在连线左侧,就将该点加入队列。最后形成的顺序序列长度应当为n+1,因为要回到最低点,所以最后要补一个最低点,并对n做调整。
1 int cal(int x1,int x2,int y1,int y2,datatype p){ 2 return (x2-x1)*p.y-y1*(x2-x1)-(y2-y1)*p.x+x1*(y2-y1); 3 } 4 void geometry_sort(int n){ 5 sort(point+1,point+n+1,com); 6 a[1]=point[1]; 7 int m=1; 8 int x1=point[1].x; 9 int x2=point[n].x; 10 int y1=point[1].y; 11 int y2=point[n].y; 12 for(int i=2;i<n;i++) 13 if(cal(x1,x2,y1,y2,point[i])<=0) 14 a[++m]=point[i]; 15 a[++m]=point[n]; 16 for(int i=n-1;i>=1;i--) 17 if(cal(x1,x2,y1,y2,point[i])>=0) 18 a[++m]=point[i]; 19 return; 20 }
确定遍历顺序后,按顺序枚举点,同时使用一个栈来记录结果。每次枚举到一个新点后,如果栈内存有不到2个点,就直接入栈,不考虑叉积,如果栈内存有2个以上点,就将新点和栈顶的两个点进行计算,确定路线叉积,如果叉积小于或等于0,说明无法构成凸多边形,就将栈顶点出栈(注意,此时新点还没有入栈),让新点继续和剩下的栈测试,直到不足2个点或者叉积大于0.
1 datatype st[101]; 2 int top; 3 int cross_product(datatype v1,datatype v2){ 4 return v1.x*v2.y-v1.y*v2.x; 5 } 6 bool check(datatype tmp){ 7 datatype x1=st[top-1]; 8 datatype x2=st[top]; 9 datatype x3=tmp; 10 datatype v1=datatype(x2.x-x1.x,x2.y-x1.y); 11 datatype v2=datatype(x3.x-x2.x,x3.y-x2.y); 12 return cross_product(v1,v2)>0; 13 }
1 geometry_sort(n); 2 n+=1; 3 top=0; 4 for(int i=1;i<=n;i++){ 5 while(top>=2&&!check(a[i])) 6 top-=1; 7 st[++top]=a[i]; 8 }
最后得到的栈,从栈底到栈顶的序列就是凸包的逆时针序列。