前言:
首先,什么是凸包?
说凸包首先要说凸性的定义,简单点说就是平面邻域中任意两点所在的线段上的点都在该邻域中,则该邻域具有凸性。简单推敲一下,就可以发现如果邻域中存在一阶导数不连续的点一定无法被某点集线性表示出来。再往下的内容属于数学分析了,对我们的算法设计帮助不大,暂时先不管。
假设平面上有p0~p12共13个点,过某些点作一个多边形,使这个多边形能把所有点都“包”起来。当这个多边形是凸多边形的时候,我们就叫它“凸包”。如下图:
如何判断一个点 p3 是在直线 p1p2 的左边还是右边呢?(坐标:p1(x1,y1),p2(x2,y2),p3(x3,y3))
利用叉积
(x2-x1)*(y3-y1)-(x3-x1)*(y2-y1)#include<stdio.h> #include<math.h> #include<algorithm> using namespace std; int n,top; struct point { int x,y; } p[1005],s[1005],t; double cross(point t1,point t2,point t3,point t4) //求向量t1t2和向量t3t4的叉积 { return (t2.x-t1.x)*(t4.y-t3.y)-(t2.y-t1.y)*(t4.x-t3.x); } double dis(point t1,point t2) //求距离 { double z=(t2.x-t1.x)*(t2.x-t1.x)+(t2.y-t1.y)*(t2.y-t1.y); return sqrt(z); } bool cmp(point t1,point t2) { double z=cross(p[0],t1,p[0],t2); return z?z>0:dis(p[0],t1)>dis(p[0],t2); } void findpoint() //找基点,按y从小到大,如果y相同,按x从左到右 { int i,j=0; t=p[0]; for(i=1; i<n; i++) { if(t.y>p[i].y||(t.y==p[i].y&&t.x>p[i].x)) { j=i; t=p[i]; } } t=p[0]; p[0]=p[j]; p[j]=t; } void scanner() { int i; findpoint(); sort(p+1,p+n,cmp); s[0]=p[0]; s[1]=p[1]; top=1; for(i=2; i<n; i++) { while(cross(s[top-1],s[top],s[top],p[i])<0) top--; top++; s[top]=p[i]; } } int main() { int i; double ans; while(~scanf("%d",&n)) { for(i=0; i<n; i++) { scanf("%d%d",&p[i].x,&p[i].y); } scanner(); for(i=0; i<=top; i++) { printf("(%d,%d)\n",s[i],s[(i+1)%(top+1)]); } } return 0; } /* 输入 14 30 30 50 60 60 20 70 45 86 39 112 60 200 113 250 50 300 200 130 240 76 150 47 76 36 40 33 35 输出 (60,20) (250,50) (300,200) (130,240) (76,150) (47,76) (30,30) */
解五:Melkman算法
Melkman算法迄今为止最好的凸包算法了,我强烈推荐的算法。它的基本操作和Graham-Scan一样,只不过它在任意时候都求得当前已考察点所形成的凸包,所以它有一个无可比拟的优势就是它是一个在线算法(要想往点集中增加一个点不必重新计算)。对于给定一个多边形,它可以直接求其凸包而不用先有序化。但对于点集,还是要先排序再用Melkman算法。
将Pi从左压入d
代码实现:
#include<stdio.h> int g_result[240][2]; /*getResult()实现功能:以坐标P0(x1,y1)和Pn(x2,y2)为直线,找出pack里面里这条直线最远的点Pmax *并找出直线P0Pmax和PmaxPn的上包,进行递归。 *注:Pack[0][0]存放点的个数,pack[1]开始存放点的坐标。 *全局变量g_result[][]用来存放凸包上的点,即最终所要的答案。同样g_result[0][0]存放的是已找到的点的个数。 **/ void getResult(int Pack[240][2], int x1, int y1, int x2, int y2) { int i,t,x3,y3,R,Rmax,tmax; int ResultPack[240][2]; ResultPack[0][0] = 0; if(Pack[0][0] <= 1) return; x3 = Pack[1][0]; y3 = Pack[1][1]; R = x1*y2 + x3*y1 + x2*y3 - x3*y2 - x2*y1 - x1*y3; Rmax = R; tmax = 1; for(i=2;i<=Pack[0][0];i++) { x3 = Pack[i][0]; y3 = Pack[i][1]; R = x1*y2 + x3*y1 + x2*y3 - x3*y2 - x2*y1 - x1*y3; if(R >= 0) { t = ++ResultPack[0][0]; ResultPack[t][0] = x3; ResultPack[t][1] = y3; } if(R > Rmax) { Rmax = R; tmax = i; } } if(Rmax <= 0) { for(i=1;i<ResultPack[0][0];i++) { x3 = ResultPack[i][0]; y3 = ResultPack[i][1]; R = x1*y2 + x3*y1 + x2*y3 - x3*y2 - x2*y1 - x1*y3; if(R == 0 && !((x3==x2&&y3==y2)||(x3==x1&&y3==y1))) { t = ++g_result[0][0]; g_result[t][0] = ResultPack[i][0]; g_result[t][1] = ResultPack[i][1]; } } return; } else { t = ++g_result[0][0]; g_result[t][0] = Pack[tmax][0]; g_result[t][1] = Pack[tmax][1]; if(ResultPack[0][0] == 0) return; } getResult(ResultPack,x1,y1,Pack[tmax][0],Pack[tmax][1]); getResult(ResultPack,Pack[tmax][0],Pack[tmax][1],x2,y2); } int main() { int Point[240][2];//Point存所有点。 int i=1,n; int x1,y1,x2,y2,x3,y3; g_result[0][0]=0;Point[0][0]=0;//Point的第一行第一列元素存放包里面有几个点。初始化为0。 printf("请输入所有点的个数及坐标:\n"); scanf("%d",&n); for(i=1;i<=n;i++) scanf("%d%d",&Point[i][0],&Point[i][1]); Point[0][0] = i-1; x1 = Point[1][0]; y1 = Point[1][1]; x2 = x1; y2 = y1; for(i=2;i<=Point[0][0];i++) { x3 = Point[i][0]; y3 = Point[i][1]; if(x3 < x1) { x1 = x3; y1 = y3; } else if(x3 > x2) { x2 = x3; y2 = y3; } } g_result[1][0] = x1; g_result[1][1] = y1; g_result[2][0] = x2; g_result[2][1] = y2; g_result[0][0] += 2; getResult(Point, x1, y1, x2, y2); getResult(Point, x2, y2, x1, y1); printf("\n\n构成凸包的点有:\n"); for(i=1;i<=g_result[0][0];i++) printf("(%d,%d)\n",g_result[i][0],g_result[i][1]); } /* 输入 14 30 30 50 60 60 20 70 45 86 39 112 60 200 113 250 50 300 200 130 240 76 150 47 76 36 40 33 35 输出 (30,30) (300,200) (130,240) (76,150) (47,76) (250,50) (60,20) */