共线凸包 极坐标排序和水平排序的抉择

凸包的原理,这里就不多介绍了.

前几天做多校的时候遇到一道共线的凸包问题. 由于自己图样, 直接用极坐标排序的模板上去做,wa了一天. 

然后就到处找资料, 看别人代码,看书.  

终于知道了,凸包的 极坐标排序 无法解决共线问题.


一般凸包的极坐标排序,都是找到最左下(y首先考虑)的点后放到point[ 0 ],根据  point[ 0 ]->point[ a ] 和 point[ 0 ]->point[ b ]  叉乘的值排序 ,  最后排序结果是把point[ 0 ]  看做原点,其他的点按顺序逆时针分部的. 如果 三点共线,  那么, 按到point[ 0 ] 的距离从小到大排序.

共线凸包 极坐标排序和水平排序的抉择_第1张图片

排序如图A 为point[ 0 ], B C Q.... P D E 都是根据极坐标排序 先后找到的点.


接下来 来讲下极坐标排序的弊端(详见lrj黑书P395)  .  


极坐标摸板里看到第73行中的 while(top>0&&cross(list[stack[top-1]],list[stack[top]],list[i])<=0) top--;

不要共线的点的情况下

这一行的意义在于 如果stack 里面有两个或者以上的点.  那取stack[top-1]为中心点 .   stack[top]为a点,  list是正要入栈的点为b点.

以ABC点为例,在没去掉while()里面的=号之前,这个模板,会删去stack[top]点, 也就是B点, 这样可以选着最少的点,最后留下的是AC点

在看末尾对应的PED点, D点因为离A近,所以先入栈,当E点再入栈的时候, D点会出栈. 所以 最后留下的是PEA

可以见得极坐标排序,如果不需要有共线的点存在的话,是可行的.



如果是要把共线的点也进入stack栈的话 , 得去掉while()中的" ="  

如果排序后,选离A远的先入栈, 这显然不行的,以这样的入栈顺序, 在判断CBQ的时候会把B点删去.

如果排序后,选离A进的先入栈, 这显然也不行,在判断PDE时,D点会被删去.

所以,在需要存共线点的情况下,极坐标排序是不行的.


所以在要算共线点的题目中,要用水平排序.







这是极坐标模板;

#include
#include
#include
#include
using namespace std;
const int MAXN=1005;//点数   //复杂度nlogn

struct point
{
    int x,y;
};
point list[MAXN];
int stack[MAXN],top;

int cross(point p0,point p1,point p2) //计算叉积  p0p1 X p0p2 
{
    return (p1.x-p0.x)*(p2.y-p0.y)-(p1.y-p0.y)*(p2.x-p0.x);
}    
double dis(point p1,point p2)  //计算 p1p2的 距离 
{
    return sqrt((double)(p2.x-p1.x)*(p2.x-p1.x)+(p2.y-p1.y)*(p2.y-p1.y));
}    
bool cmp(point p1,point p2) //极角排序函数 , 角度相同则距离小的在前面 
{
    int tmp=cross(list[0],p1,p2);  //逆时针
    if(tmp>0) return true;
    else if(tmp==0&&dis(list[0],p1)list[i].y) || ((p0.y==list[i].y)&&(p0.x>list[i].x)) )
        {
            p0.x=list[i].x;
            p0.y=list[i].y;
            k=i;
        }    
    }    
    list[k]=list[0];
    list[0]=p0;
    
    sort(list+1,list+n,cmp);//极角排序  // 逆时针
}     

void graham(int n) // stack 里存的是凸包外围点的下标. [0,top] 
{
    int i;
    if(n==1) {top=0;stack[0]=0;}
    if(n==2)
    {
        top=1;
        stack[0]=0;
        stack[1]=1;
    }    
    if(n>2)
    {
        for(i=0;i<=1;i++) stack[i]=i;
        top=1;
        
        for(i=2;i0&&cross(list[stack[top-1]],list[stack[top]],list[i])<=0) top--;//大方向 逆时针
            top++;             //==0时会去掉短的,延长出 更长的线.  <0时,以top-1点为中心,连接top点和当前i点. 若转向为顺时针, 那就去掉top点. 这样保证是最外的.
            stack[top]=i;
        }    
    }    
}

double sumlen()//自己打的画蛇添竹的周长计算函数
{
	double sum=0;
	for(int i=1;i<=top;i++)
	{
		sum+=dis(list[stack[i-1]],list[stack[i]]);
	}
	sum+=dis(list[stack[0]],list[stack[top]]);
	return sum;
}



这是水平排序的模板,代码转自http://blog.csdn.net/a601025382s/article/details/11659113

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
const int maxn=1010;
const double pi=atan(1.0)*4;
struct node{
    int x,y;
}e[maxn],res[maxn];
int m;
int cmp(node a,node b)
{
    if(a.x==b.x)return a.y1&&cross(res[m-1],e[i],res[m-2])<0)m--; //有等于就不能有共线,也没有重点
        res[m++]=e[i];
    }
    k=m;
    //求得上凸包
    for(i=n-2;i>=0;i--)
    {
        while(m>k&&cross(res[m-1],e[i],res[m-2])<0)m--;
        res[m++]=e[i];
    }
    if(n>1)m--;//起始点重复。
    return m;
}
 
int main()
{
    int T,n,l;
    scanf("%d",&T);
    while(T--)
    {
        int i,j,k;
        scanf("%d",&n);
        for(i=0;i


你可能感兴趣的:(凸包)