凸包是计算几何中的一个基本概念。在竞赛中,很少单独考察凸包,但求凸包是很多题目求解的一个关键性步骤。
1)凸包的性质
给定一个点集,凸包是能够包围所有点的最小凸多边形。”凸包边上的点,称为凸包点,其余点称为凸包内点“(引自何援军著《几何计算》)
凸包有一些区别于普通多边形的重要性质:
1.所有的顶点均在任何一条凸包边所在直线的一侧,如果逆时针遍历凸包的边,则对每条边,所有点均在其左侧。
2.从任一点出发,沿逆时针遍历凸包,总是向左转,沿顺时针遍历凸包,总是向右转(即叉乘的符号在沿同一方向运动时是不变的)。
3.凸包对凸包点排序,即选定一条边,凸包上的点依次与该边所在直线的距离成单峰函数。
2)凸包求法
经常会遇到这样的问题:给定平面上的一些点,求这些点对应的凸包。
此处暂介绍一种方法:Graham扫描法,还有其他方法,这里不再介绍。
步骤如下:
1.将点按照x坐标排序,x坐标相同就按y坐标排序。
2.第一个点必定是凸包中的点,将其压入栈S中。
3.对下一个点进行判断,如果栈内元素小于2个,则将该点直接加入栈中;否则,进行叉积判断。如果是进行逆时针判断,只要遇到向右转的情况,就从栈中弹出一个点,直到栈中只剩一个点或者出现左转。重复此步骤,直到n个点全部遍历完毕。
4.从n开始,向前遍历,遍历方法如3,直到遍历到第一个点。
整体过程如下图所示:
这时遇到右转,将P3弹出。
再次遇到右转情况,弹出P4。
特别提醒:如果出现三点共线的情况,最好将中间的点删去,可以让复杂度稍微小一点。
出现右转,将P5弹出。
右转,弹出P4。
回到起点,这时栈S中的元素:P1,P2,P5,P6,P3,P1,这就是整个凸包的凸包点的集合。
代码实现如下:
point s[N]; //运行结束时,凸包点按顺序存于s中,n为凸包顶点的个数
double cp(point a,point b,point o) //叉积
{
return (a-o)*(b-o);
}
void convex(point *p,int &n) //参数p为输入点,n为输入点个数
{
sort(p,p+n); //将点按x坐标排序,如果x相同按y坐标排序
int i,j,r,top,m;
s[0]=p[0];s[1]=p[1];top=1;
for(i=2;i<n;++i) //从前向后扫描
{
while(top>0&&cp(p[i],s[top],s[top-1])>=0)
top--;
top++;s[top]=p[i];
}
m=top; //记录当前栈s里元素的个数
top++;s[top]=p[n-2];
for(i=n-3;i>=0;i--) //从后向前扫描
{
while(top>m&&cp(p[i],s[top],s[top-1])>=0)
top--;
top++;s[top]=p[i];
}
top--;
m=top+1;
}
【习题推荐】
HDU 3629
HDU 1348