要求将点集顺时针连接,且线不交叉,输出最多能连的点数,并输出路径。
由于最近一直在看凸包问题,所以读完题,首先想到Graham法,不过Graham法用得比较麻烦。后来上网看了下解题报告,原来卷包裹法才是正解,于是用卷包裹法又解了一遍。这里把这两种方法都写一写吧。
Graham法:递归求每层凸包,每层凸包的最后一点A,要去找下一层凸包与A向左转最小角的点做为下一点B,(因为题目要求逆时针,并不交叉)即下一个凸包起点。想想其实和卷包裹法思想差不多;
卷包裹法:以最左下方的点为起点A,找其余与A向左转最小角的点做为下一点B,然后用同样的方法再去找B的下一个点,直到找完所有点。是不是要比上面的Graham法简便多了。
1、Graham法
Source Code
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
int num,top;
struct node
{
int x;
int y;
int z;
}a[55],b[50],c[55]; //a顶点输入,之后还用来记录未进入路径并不再凸包上的点,b当栈使用记录凸包顶点,c记录路径。
int cmp(const void *c,const void *d)
{
int ans;
struct node *p1=(node *)c;
struct node *p2=(node *)d;
ans=(p1->x-a[0].x)*(p2->y-a[0].y)-(p2->x-a[0].x)*(p1->y-a[0].y);
if (ans>0) return -1;
else if (ans<0) return 1;
else
{
if (abs(a[0].x-p1->x)<abs(a[0].x-p2->x)) return -1;
else return 1;
}
}
int find( int p2,int p1,int p0) //叉乘判断转向
{
return (b[p1].x-b[p0].x)*(a[p2].y-b[p0].y)-(a[p2].x-b[p0].x)*(b[p1].y-b[p0].y);
}
int find2( int p2,int p1,int p0)
{
return (b[p1].x-c[p0].x)*(a[p2].y-c[p0].y)-(a[p2].x-c[p0].x)*(b[p1].y-c[p0].y);
}
void ok(int n)
{
int i,minx=50005,miny=50005;
for(i=0;i<n;i++)
if (a[i].y<miny || (miny==a[i].y && a[i].x<minx)) //记录y值最小的点 有多个时 选x最小的
{
miny=a[i].y;minx=a[i].x;num=i;
}
a[n]=a[num]; //将y最小的点当成极坐标的原点
a[num]=a[0];
a[0]=a[n];
}
void Graham(int n)
{
int d,to=1,i,j;
ok(n);
qsort(a+1,n-1,sizeof(a[0]),cmp);
b[0]=a[0];
b[1]=a[1];
j=0;
for (i=2;i<n;i++)
{
while (to!=0&&find(i,to,to-1)<0) //如果向右转,就把栈顶退一个,直到新加入的点是向左转为止,包括等零
{
a[j]=b[to];
j++;
to--;
}
b[++to]=a[i]; //进栈
}
a[n]=b[0];
d=0;
if(top!=-1)
for(i=1;i<=to;i++)
if(find2(n,i,top)>0||(find2(n,i,top)==0&&(pow(b[i].x-c[top].x,2)+pow(b[i].y-c[top].y,2)<pow(a[n].x-c[top].x,2)+pow(a[n].y-c[top].y,2))))
{
a[n]=b[i];
d=i;
}
for(i=d;i<=to;i++)
c[++top]=b[i];
for(i=0;i<d;i++)
c[++top]=b[i];
if(j==1)
c[++top]=a[0];
else if(j>1) Graham(j);
}
main ()
{
// freopen ("1.txt","r",stdin);
int n,i,t;
scanf("%d",&t);
while(t--)
{
top=-1;
scanf("%d",&n);
for (i=0;i<n;i++)
scanf("%d%d%d",&a[i].z,&a[i].x,&a[i].y);
ok(n);
qsort(a+1,n-1,sizeof(a[0]),cmp); //按极角由小到大(逆时针)排序,有多个极角相同的,按离原点近的排
Graham(n);
printf("%d",top+1);
for(i=0;i<=top;i++)
{
printf(" %d",c[i].z);
}
printf("\n");
}
system("pause");
return 0;
}
2、卷包裹法
Source Code
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
int n,num;
struct node
{
int x;
int y;
int z;
int flag;
}a[55],b[50]; //b当栈使用
int find( int p2,int p1,int p0) //叉乘判断转向
{
return (a[p1].x-b[p0].x)*(a[p2].y-b[p0].y)-(a[p2].x-b[p0].x)*(a[p1].y-b[p0].y);
}
bool dis(int i,int num,int ans)
{
return pow(a[i].x-b[ans].x,2)+pow(a[i].y-b[ans].y,2)<pow(a[num].x-b[ans].x,2)+pow(a[num].y-b[ans].y,2);
}
main ()
{
// freopen ("1.txt","r",stdin);
int ans,i,t,miny,minx;
scanf("%d",&t);
while(t--)
{
miny=5000,minx=5000;
scanf("%d",&n);
for (i=0;i<n;i++)
{
scanf("%d%d%d",&a[i].z,&a[i].x,&a[i].y);
a[i].flag=1;
if (a[i].y<miny || (miny==a[i].y && a[i].x<minx)) //记录y值最小的点 有多个时 选x最小的
{
miny=a[i].y;minx=a[i].x;num=i;
}
}
b[0]=a[num];
ans=0;
a[num].flag=0;
while(ans!=n-1)
{
for(i=0;i<n;i++)
if(a[i].flag)
{
num=i;
break;
}
for(i=0;i<n;i++)
if(i!=num&&a[i].flag&&(find(i,num,ans)<0||(find(i,num,ans)==0&&dis(i,num,ans))))
num=i;
b[++ans]=a[num];
a[num].flag=0;
}
printf("%d",ans+1);
for(i=0;i<=ans;i++)
printf(" %d",b[i].z);
printf("\n");
}
system("pause");
return 0;
}