一、点集有序化-水平排序
在计算几何中,点集往往无序,因此在计算前需要对点集进行排序,使得算法可以有序高效运行。
水平排序利用点在二维平面上固有的横纵坐标属性进行排序,只涉及点坐标的比较,与极坐标排序使用的三角函数相比没有精度问题。
水平排序利用的规则为,纵坐标为第一关键字,横坐标为第二关键字,纵坐标升序排列,如果纵坐标相等,按横坐标升序排列。
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 }
最后得到的栈,从栈底到栈顶的序列就是凸包的逆时针序列。