二维计算几何 学习笔记

1.基础知识

1.1关于浮点误差

一般的话要设一个 EPS=1e8 E P S = 1 e − 8

//精度控制 
bool Dcmp(double x)
{
    if(fabs(x)<Eps) return 0;
    else return 1;
}

1.2向量基础运算

1.2.1点积

double Dot(Vector A,Vector B) { return A.x*B.x+A.y*B.y; }

点积可以用来判断线段垂直什么的.

1.2.2叉积

double Cross(Vector A,Vector B){ return A.x*B.y-A.y*B.x; }

叉积表示两个向量组成的平行四边形的有向面积.
设两个向量 v1,v2 v 1 , v 2
v1 v 1 v2 v 2 顺时针方向时,叉积大于0.
v1 v 1 v2 v 2 逆时针方向时,叉积小于0.
v1,v2 v 1 , v 2 共线时,叉积等于0.

1.2.3叉积点积方向.

这里写图片描述

2.进阶知识

2.1点直线线段关系

2.1.1点到直线的距离

一般的话要三个点,点一个点,直线上两个点。
直接用叉积求出面积来除以底边的长度就可以

//点到直线距离
double PointDisToLine1(Point P,Point A,Point B)
{
    Vector v1=B-A,v2=P-A;
    return fabs(Cross(v1,v2))/Length(v1);
} 

2.1.2点到线段的距离

点到线段的距离分为三种情况:
红色为点到线段距离
1.当点位于线段上方时.
这里写图片描述
2.当点位于线段左边时.
这里写图片描述
3.当点位于线段右边时.
这里写图片描述

//点到线段距离 
double PointDisToLine2(Point P,Point A,Point B)
{
    if(A==B) 
        return Length(P-A);
    Vector v1=B-A,v2=P-A.v3=P-B;
    if(Dcmp(v1,v2)<0) return Length(v2);
    else if(Dcmp(v1,v3)<0) return Length(v3);
    else return fabs(Cross(v1,v2))/Length(v1); 
} 

2.1.3线段相交

我们判断线段相交直接判断一个线段两个端点是否位于另一个线段的两侧就可以.
这里写图片描述
假如线段相交的话,那么 Vb1a1 V b 1 a 1 叉乘 Va1a2 V a 1 a 2 Vb2a1 V b 2 a 1 叉乘 Va1a2 V a 1 a 2 的符号一定不同,
同理另一符号也一定不同,所以我们可以通过叉积的符号来判断线段相交.

//线段相交判定
bool LineIntersection(Point a1,Point a2,Point b1,Point b2)
{
    double c1=Cross(a2-a1,b1-a1),c2=Cross(a2-a1,b2-a1),
           c3=Cross(b2-b1,a2-b1),c4=Cross(b2-b1,a2-b1);
    return Dcmp(c1)*Dcmp(c2)<0 && Dcmp(c3)*Dcmp(c4)<0; 
} 

2.1.4直线交点的计算

对于直线我们设直线的方程 l1=P0+tv l 1 = P 0 + t v ,其中 P0 P 0 表 示 一 个 点 v v 表示方向向量
我们设两个直线分别是P+tv Q+tw Q + t w ,设 u=PQ u = P − Q
t1=Cross(w,u)Cross(v,w),t2=Cross(v,u)Cross(v,w) t 1 = C r o s s ( w , u ) C r o s s ( v , w ) , t 2 = C r o s s ( v , u ) C r o s s ( v , w )
带入求解即可.

//直线交点的计算
Point GetLineIntersection(Point P,Vector v,Point Q,Vector w)
{
    Vector u=P-Q;
    double t=Cross(w,u)/Cross(v,w);
    return P+v*t;
}

2.2多边形

2.2.1多边形的面积

怎样计算多边形的面积呢?
我们可以把多边形来划分成数个三角形,然后求叉积就可以了.
这里写图片描述
对于上图,我们要求 Sn1n2n3n4n5 S n 1 n 2 n 3 n 4 n 5 的话,我们只需要求出 (n1n3×n1n2)+(n1n4×n1n3)+(n1n5×n1n4) ( n 1 n 3 × n 1 n 2 ) + ( n 1 n 4 × n 1 n 3 ) + ( n 1 n 5 × n 1 n 4 ) 即可
这个可以通过 for() f o r ( · · · · ) 来实现,实现起来比较简单.
上面的例子是凸多边形,假如我们要求的不是凸多边形呢?
如下图:
这里写图片描述
我们设 Sn1n2n3=S1,Sn1n4n3=S2,Sn1n5n4=S3 S n 1 n 2 n 3 = S 1 , S n 1 n 4 n 3 = S 2 , S n 1 n 5 n 4 = S 3
我们发现由于我们计算的时候是按照顶点的顺序来计算的,所以我们计算叉积的时候会存在正负的关系,所以对于不是凸多边形的情况,我们依然可以按照上面的方法计算比如说上图 S=S1S2+S3 S = S 1 − S 2 + S 3 ,我们计算 S2 S 2 的时候恰好叉积为负。
最后不要忘记除2哦,因为计算的是平行四边形的有向面积

//多边形面积
double S_ploy(Point p[],int n)
{
    double S=0;
    for(int i=3;i<=n;i++)
        S+=Cross(p[i]-p[1],p[i-1]-p[1]);
    return S/2;
} 

2.2.2关于点在多边形内的判断.

一般的话采用射线法,我们只需要判断穿过多边形的数目就可以

//判断点是否在多边形内
int Point_In_Poly(Point P,Point poly[],int num)
{
    int ans=0,k,d1,d2;
    for(int i=1;i<=num;i++)
    {
        if(!Dcmp(Point_Dis_To_1(P,poly[i],poly[i%num+1]))) return -1;
        k=Dcmp(Cross(ploy[i%num+1]-poly[i],p-poly[i]));
        d1=Dcmp(ploy[i].y-p.y);
        d2=Dcmp(poly[i%num+1].y-p.y);
        if(k>0 && d1<=0 && d2>0 ) ++ans;
        if(k<0 && d1>=0 && d2<0) --ans;   
    }
    if(ans) 
        return 1;
    return 0;
}

3.高级知识

3.1凸包

3.1.1定义

凸包是啥呢?
给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点.
这里写图片描述
上面红色的就是一个凸包。

3.1.2Andrew算法

Andrew A n d r e w Graham G r a h a m 算法的变种,但是 Andrew A n d r e w 更快.
算法流程:
1.就是先对所有点进行排序,以 x x 为第一关键字,以y为第二关键字
2.按照顺序依次遍历所有点,看是否符合要求:
这里写图片描述
怎么看是否符合要求呢,我们假设 A,B,C,D A , B , C , D 已经是凸包中的点了,现在我们将要插入 E E ,这是我们要判断凸包中倒数第一个点与倒数第二个点组成的向量,与即将插入的点与倒数第二个点组成的向量的叉积的正负,若为负,也就是上图中的情况((D,C)×(E,C))<0,我们就退出凸包中的最后一个点,因为这个点已经不可能在凸包内了,然后我们不停的判断直达叉积大于0,就可以把当前要插入的点插入了。
搞完这一个点之后就是这个样子了:
这里写图片描述
3.这样按照排序遍历一遍之后我们只是求得了下凸包,也就是凸包的一半,所以我们还要再倒序枚举一遍,求出上凸包来
4.凸包就求完了。

//凸包
int ConvexHull(Point P[],int n)
{
    sort(p,p+n);
    int m=0;
    for(int i=;i<n;i++)
    {
        while(m>1 && Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2])<=0) m--;
        ch[m++]=p[i];
    }
    int k=m;
    for(int i=n-2;i>=0;i--)
    {
        while(m>k) && Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2]<=0) m--;
        ch[m++]=p[i];
    }
    if(n>1) m--;
    return m;
} 

3.2旋转卡壳

不要问我怎么读,我也不会读..

3.2.1旋转卡壳

旋转卡壳是用来求平面内,最远点的距离的(就是距离最远的两个点的距离)。
有一个性质非常显然:最远点对一定在凸包上,这种需要我们先求出凸包来.
考虑暴力枚举: O(n2) O ( n 2 )
然后我们来看旋转卡壳:

//旋转卡壳
double RotatingCaliper(Point ch[],int n)
{
    int q=1; double ans=0;
    ch[n]=ch[0];
    for(int p=0;p<n;p++)
    {
        while(fabs(Cross(ch[q+1]-ch[p+1],ch[p]-ch[p+1])) > fabs(Cross(ch[q]-ch[p+1],ch[p]-ch[p+1]))) q=(q+1)%n;
        ans=max(ans,max(Length2(ch[p]-ch[q]),Length2(ch[p+1]-ch[q+1])));
        ans=max(ans,max(Length2(ch[p]-ch[q+1]),Length2(ch[p+1]-ch[q])));
    }
    return ans;
} 

3.3半平面交

半平面交
极角:从x正半轴旋转到当前直线的角度 函数cmath atan2()
极角排序:
用双端队列维护
然后每一个考虑加入一条直线,
如果新加入的直线的与倒数第二条直线的交点在倒数第一条直线与倒数第二条直线的交点在新加入直线的右边那么把倒数第一条直线退队.还要和队首判断一下,假如队首第一条直线的交点和队首第二条直线的交点在新加入直线的右边退队..
然后拿队首第一条直线和倒数第二条直线判断一下就可以.

//半平面交
struct Line()
{
    Point P;
    Vector v;
    double ang;
    Line() {}
    Line(Point P,Vector v) : P(P),v(v){ ang=atan2(v.y,v.x); }
    bool operator < (const Line &L) const
    {
        return ang<L.ang;
    }
} 
bool OnLeft(Line L,Point p)
{
    return Cross(L.v,p-L.P)>0;
}
int HalfPlane(Line L[],int n,Point poly[])
{
    sort(L,L+n);
    int first,last;
    Point p[MAXN];
    Line q[MAXN];
    q[first=last=0]=L[0];
    for(int i=1;i<n;i++)
    {
        while(first<last && !OnLeft(L[i],p[last-1]))) last--;
        while(first<last && !OnLeft(L[i],p[first])) first++;
        q[++last]=L[i];
        if(!Dcmp(Cross(q[last].v,q[last-1].v)))
        {
            last--;
            if(OnLeft(q[last],L[i].P)) 
                q[last]=L[i];
        }
        if(first<last) p[last-1]=GetLineIntersection(q[last-1],q[last]);
    }
    if(last-first<=1) return 0;
    p[last]=GetLineIntersection(q[last],q[first]);
    int m=0;
    for(int i=first;i<=last;i++) poly[m++]=p[i];
    return m;
    ···; 
} 

3.4最小圆覆盖

这里写图片描述

3.5最近点对

就是求平面中最近的两个点,怎么求呢可以 n2 n 2 暴力,还可以用分治的方法来求,怎么分治,每一次分成左边和右边,最近点对只有三种情况一种是在左边,一种是在右边,一种是在中间,所以我们先求出左边和右边的最近点对,跨过中间的最近点对只能在左右的距离,肯定小于左右最近点对距离然后把在范围的点按y排序,求出距离来就可以了. O(nlogn) O ( n l o g n )

double Closest_Pair(int left,int right)
{
    double dis=INF;
    if(left==right)
        return dis;
    if(left+1==right)
        return dist(p[left],p[right]);
    int mid=(left+right)>>1;
    double dis1=Closest_Pair(left,mid);
    double dis2=Closest_Pair(mid,right);
    dis=min(dis1,dis2);
    int num=0;
    for(int i=left;i<=right;i++)
    {
        if(fabs(p[mid].x-p[i].x<=dis))
            now[++num]=p[i];    
    }
    sort(now+1,now+num+1,Y);
    for(int i=1;i<=num;i++)
    {
        for(int j=i+1;j<=num && now[j].y-now[i].y<dis;j++)
        {
            double dis3=Dis(now[i],now[j]);
            dis=min(dis,dis3);
        }   
    }   
    return dis;
} 
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值