格雷厄姆扫描法是利用平面上任意3 点所构成的回路是左转还是右转的判别法来求平面点集的凸包。
1.三角区符号的计算
点o(a,b),p(c,d),q(e,f)是平面上的任意三点,D=ab+cf+eb-bc-de-fa
当D 为正时,o,p,q,o 构成一个反时针方向的回路,即o,p,q,是左转的;当D 为负时,o,p,q,o 构成一个顺时针方向的回路,即o,p,q 是右转的;当D 为零时,此三点共线。
3.算法步骤:
(1)求平面点集S 中Y 坐标最小的点p0。
(2)以p0 为源点,变换S-{p0}中所有点的坐标
(3)以p0 为源点,计算S-{p0}中所有点的幅角。
(4)以幅角的非降排序S-{p0}中所有的点,令事件调度点T={p1,p2,…, pn-1}是排序过的数组。
(5)初始化堆栈:令CHS[0]=pn-1,CHS[1]=p0;令堆栈指针sp=1,事件调度点数组T的下标k=0。
(6)如果k<n-1,转步骤(7);否则,算法结束。
(7)计算CHS[sp -1], CHS[sp] = p0 ,T[k]所构成的三角区符号D,若D >= 0,sp = sp+1,CHS[sp] =T[k],k=k+1,转步骤(6);否则,sp=sp-1;转步骤(6)。
算法实现
struct Point { int x; int y; }; struct SPoint { int x; int y; float ang; //tan值 }; void Sort(SPoint T[],int n) { SPoint temp; for(int i=0;i<n;i++) for(int j=i+1;j<n;j++) { if(T[i].ang<T[j].ang) { temp=T[i]; T[i]=T[j]; T[j]=temp; } } } void Convex_hull(Point s[],Point CHS[],int n,int &sp) { int i,k; float D; Point temp; SPoint T[18]; for(i=1;i<n;i++) //s中Y坐标最小的点与S[0] if((s[i].y<s[0].y) || ((s[i].y==s[0].y) && s[i].x<s[0].x)) { temp=s[0]; s[0]=s[i]; s[i]=temp; } for(i=1;i<n;i++) //以s[0]为源点,变换s[i]的坐标于T[i-1] { T[i-1].x=s[i].x-s[0].x; T[i-1].y=s[i].y-s[0].y; double at=T[i-1].y/T[i-1].x; T[i-1].ang=atan(at); } Sort(T,n-1); //按幅角大小排序 CHS[0].x=T[n-2].x; CHS[0].y=T[n-2].y; CHS[1].x=0; CHS[1].y=0; sp=1; k=0; while(k<n-1) //求栈顶两点及扫描线上一点构成的三角符号 { D=CHS[sp-1].x*CHS[sp].y+CHS[sp-1].y*T[k].x+CHS[sp].x*T[k].y-CHS[sp-1].y *CHS[sp].x-CHS[sp].y*T[k].x-T[k].y*CHS[sp-1].x; if(D>=0) { CHS[sp].x=T[k].x; CHS[sp].y=T[k].y; sp++; k++; } else sp--; } }