欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【笔记篇】最良心的计算几何学习笔记(一)

程序员文章站 2022-04-01 13:32:33
...

世界以痛吻我, 我却报之以歌。

开新坑…
虽然不知道这坑要填多久…
文章同步上传到github…
有想看的可以去看看→_→

*温馨提示:
1. 看本文之前请务必学习或回顾数学-必修2解析几何数学-必修4平面向量有关内容…
2. 本文中的代码基本都是口胡的, 不过还是用了IDE, 只保证能过编译, 不保证正确OvO…

一 浮点数相关…

众所周知, 浮点数的运算是有误差的…所以有时候会出现一些很蛋疼的事情.. 比如两个该相等的东西会得出不相等的结果… 为了避免这种事情的发生, 我们要允许一些误差… 我们定义

const double eps=1e-9;

当然这里如果要求精度的话也可以开long double… 而且这个1e-9也是不一定的…
有些题目开1e-7 1e-8 1e-10都是不一定的… (诅咒所有卡精度的出题人吃方便面只有调料包)
这个eps有什么用呢…
这样我们就可以自定义一下实数的比较…
然而double作为基本变量类型是不能重载==运算符的, 这样就要写个函数, 而且像作者这么zz的人很可能会忘记调用OvO

inline int dcmp(const double &a){
    if(fabs(a)<eps) return 0;   //绝对值不超过eps即视为0(卡精度的来源)
    return a<0?-1:1;            //a<0 返回-1 a>0 返回1
}

这样我们关于double的东西就基本写完了..

二 平面向量相关

数学-必修4了没? 没有快去看…
根据平面向量基本定理, 每个向量a⃗ 都可以写成xe1+ye2的形式…
这里的e1,e2可以是任意的, 但是为了方便我们通常取正交基底(就是e1e2啦~)
每个向量都可以由数对(x,y)唯一确定…
这样我们就可以这么定义一个向量:

struct vec{
    double x,y;
};

很显然, 这个也可以表示一个点.
所以我们可以加一句这个, 当然也可以不加..

typedef vec point;

在此基础上, 我们就来定义各类运算.
1. 为了方便, 我们可以在vec结构体里加一个构造函数…

c++
struct vec{
double x,y;
vec(int p,int q):x(p),y(q){}
vec(){x=y=0.0;} //默认构造函数还是要有的..
};

  1. 向量的模?
    表示为|v⃗ |, 就是向量的长度啦~

    inline double len(const vec &a){
    return sqrt(a.x*a.x+a.y*a.y);
    }
  2. 比较基础的, 向量的四则运算.
    这个由定义显然

    vec operator +(const vec &a,const vec &b){
    return vec(a.x+b.x,a.y+b.y);
    }
    vec operator -(const vec &a,const vec &b){
    return vec(a.x-b.x,a.y-b.y);
    }
    vec operator *(const vec &a,const double &b){
    return vec(a.x*b,a.y*b);
    }
    vec operator *(const double &b,const vec &a){ //变量类型不同, 不能直接用交换律..
    return vec(a.x*b,a.y*b);
    }
    vec operator /(const vec &a,const double &b){
    return vec(a.x/b,a.y/b);
    }
  3. 然后就是点积(数量积 内积 标积)和叉积(向量积 外积 矢积)了..
    不过似乎并不考虑叉积的方向..(大概是因为二维计算几何的缘故吧..)
    (不过好像叉积的定义是“矢量叉积定义为由(0,0)、p1、p2和p1+p2所组成的平行四边形的带符号的面积”?)

    double operator ^(const vec &a,const vec &b){ //点积就用^就行了 反正应该是用不到异或的..
    return a.x*b.x+a.y*b.y;
    }
    double operator *(const vec &a,const vec &b){
    return a.x*b.y-a.y*b.x;
    }

点积的话根据必修四, 可以判断垂直.. 若p⃗ q⃗ =0, 则p⃗ q⃗ .

叉积的一个重要的作用就是判断向量的顺逆时针关系..

  • p⃗ ×q⃗ >0, 则p⃗ q⃗ 的顺时针方向;
  • p⃗ ×q⃗ <0, 则p⃗ q⃗ 的逆时针方向;
  • p⃗ ×q⃗ =0, 则p⃗ q⃗ , 但可能同向也可能反向.
    有一些小小的注意事项(虽然不一定有用)..
  • 据我猜测计算几何中没什么用的(但数学考试有用啊←_←)
    a⃗ b⃗ =|a||b|cosθ,a⃗ ×b⃗ =|a||b|sinθ
  • 点积是满足交换律的(显然), 而叉积是不满足交换律的(也显然), 不过叉积满足反交换律,
    a⃗ ×b⃗ =b⃗ ×a⃗ 
  • 点积和叉积都满足加法的分配律.
  • 显然不能连续点积, 叉积不满足结合律.
  • 其他的似乎更没啥用而且我也看不懂 想研究的可以点上方传送门去baidu看..

三 [跟多边形没关系的]各种常见的简单的判断

  1. 两点之间距离公式 先放在这里

    inline double dis(const vec &a,const vec &b){
        return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
    }

    其实写多了之后可能会更喜欢写len(a-b)…

  2. 两个向量的顺逆时针关系 见↑↑↑

  3. 折线段的拐向判断
    其实跟上面没有太大区别..
    两条有公共顶点线段p0p1p1p2(因为是折线啊),
    很显然我们可以表示两个向量v1=p2p0,v2=p1p0然后求v1×v2

    • v1×v2>0, 则折线往右拐;
    • v1×v2<0, 则折线往左拐;
    • v1×v2=0, 则p0,p1,p2三点共线.
  4. 判断点q是否在以p0,p1为对角顶点的矩形(不加说明则默认矩形边与坐标轴平行)内
    这个简单啊 只要分别判断q的横纵坐标比p0,p1的最小值大, 最大值小(当然是可以相等的)即可..

    inline bool ptInRect(const vec &q,const vec &p0,const vec &p1){
    return min(p0.x,p1.x)<=q.x&&q.x<=max(p0.y,p1.y)
        &&min(p0.y,p1.y)<=q.y&&q.y<=max(p0.y,p1.y);
    }
  5. 判断点q是否在线p0p1
    根据2. , 很显然的可以判断(qp0)×(p1p0)==0
    但这样就够了吗? 并不是… 因为q可能在延长线or反向延长线上…
    所以我们要保证q在以p0,p1为对角顶点的矩形内…(你以为我为什么要写3. ?)

    inline bool ptOnSeg(const vec &q,const vec &p0,const vec &p1){
    return (p1-p0)*q==0&&ptInRect(q,p0,p1);
    }

    这样就可以咯..

  6. 判断线段p0p1和直线q0q1是否相交
    如果线段与直线相交, 那么线段跨立直线..
    【笔记篇】最良心的计算几何学习笔记(一)

    首先 如图,我们可以得出v1=p0q0,v2=q1q0,v3=p1q0.
    由图可以看出, 我们要让p0p1放到直线q0q1的两侧, 就要让v1×v2v2×v3同号(或者有一方为0)

    所以有(v1×v2)(v2×v3)0, 也就是((p0q0)×(q1q0))((q1q0)×(p1q0))0.

     inline bool segCutLine(const vec &p0,const vec &p1,const vec &q0,const vec &q1){
        return ((p0-q0)*(q1-q0))*((q1-q0)*(p1-q0))>=0;
     }
  7. 判断两条线段p0p1,q0q1是否相交

    这个我们可以分为两步

    • 快速排斥实验
      两个线段的端点为对角顶点的两个矩形如果不相交, 那么这两条线段显然不相交.

    • 跨立实验
      明确一点: 如果两线段相交, 则两线段必然相互跨立对方.
      我们就可以根据上面的跨立方式得出要判断((p0q0)×(q1q0))((q1q0)×(p1q0))>0 (p0p1q0q1)((q0p0)×(p1q0))((p1q0)×(q1p0))>0 (q0q1p0p1)
      而当前面这一坨等于0的时候呢? 我们可以得知共线, 而由于已经通过了快速排斥实验, 所以端点一定在另一条线段上. 所以我们只要让((p0q0)×(q1q0))((q1q0)×(p1q0))0((q0p0)×(p1q0))((p1q0)×(q1p0))0就可以了 (其实好啰嗦啊)

      inline bool segCutSeg(const vec &p0,const vec &p1,const vec &q0,const vec &q1){
      if(min(p0.x,p1.x)>max(q0.x,q1.x)||min(q0.x,q0.y)>max(p0.x,p1.x)
          ||min(p0.y,p1.y)>max(q0.y,q1.y)||min(q0.y,q1.y)>max(p0.y,p1.y)) return false; //快速排斥实验
      return ((p0-q0)*(q1-q0))*((q1-q0)*(p1-q0))>=0&&((q0-p0)*(p1-p0))*((p1-p0)*(q1-p0))>=0; //跨立实验
      }
  8. 判断矩形是否在矩形中
    只需要简单的比较边界就行了.

    inline bool rectInRect(const vec &p0,const vec &p1,const vec &q0,const vec &q1){
    return min(p0.x,p1.x)<min(q0.x,q1.x)&&max(p0.x,p1.x)>max(q0.x,q1.x)
        &&min(p0.y,p1.y)<min(q0.y,q1.y)&&max(p0.y,p1.y)>max(q0.y,q1.y);
    }
  9. 判断圆是否在矩形中
    圆在矩形中的充要条件是圆心在矩形内而且圆的半径小于到矩形四边距离的最小值.
    话说圆要怎么表示? 一个点表示圆心 一个double表示半径嘛= =

    inline bool cirInRect(const vec &o,const double &r,const vec &p0,const vec &p1){
    return ptInRect(o,p0,p1)&&min(
            min(abs(min(p0.x,p1.x)-o.x),abs(max(p0.x,p1.x)-o.x)),
        min(abs(min(p0.y,p1.y)-o.y),abs(max(p0.y,p1.y)-o.y)))<r; //这么写好像清楚一点?
    }
  10. 判断点是否在圆中
    与圆心的距离小于半径啊~

    inline bool ptInCir(const vec &p,const vec &o,const double &r){
    return dis(p,o)<r;
    }
  11. 判断线段、折线、多边形是否在圆中
    圆是个凸集, 所以我们依次判断每个端点是否在圆内即可..
    代码就不给了OvO

  12. 判断圆O1是否在圆O2
    首先嘛 肯定要r1<r2… 其次呢 要有|O1O2|<=r2r1(其实好像可以和上一条一起判.. 因为负数显然不会大于距离..) (等号成立时两圆内切(突然不知道算不算在圆内了..先算是吧…(大不了到时候抠掉嘛)))

    inline bool cirInCir(const vec &o1,const double &r1,const vec &o2,const double &r2){
    return dis(o1,o2)<=r2-r1;
    }
  13. p到直线q0q1的距离,利用叉积求出平行四边形的面积, 然后算出底边长. 一比得到高即可.

     inline bool ptDisLine(const vec &p,const vec &q0,const vec &q1){
     return (p-q0)*(q1-q0)/len(q1-q0);
     }
  14. p到线段q0q1的距离,先判断垂足是否在线段上.
    是则同上, 不是则找距离两个端点中近的那个
    如何判断呢?
    【笔记篇】最良心的计算几何学习笔记(一)
    如图所示, 如果垂足在直线外, 那么夹角θ一定是钝角, 则cosθ<0
    那么e1e2=|e1||e2|cosθ<0,
    而且这样求出的θ的顶点就是较近的点..
    两边一求就好了…

    double ptDisSeg(const point &p,const point &a,const point &b){
        if(dcmp((p-a)^(b-a))<0) return dist(p,a);
        if(dcmp((p-b)^(a-b))<0) return dist(p,b);
        return fabs((p-a)*(b-a))/dist(b,a);
    }
  15. 两条相交直线(线段)的交点. (所以要先判断是否相交…)
    好像两种方法?
    一种是利用解析几何推导.
    对于直线l:Ax+By+C=0和直线上的两点(x0,y0),(x1,y1)
    斜率k=AB=y0y1x0x1,
    那我们不妨设A=y0y1,B=x1x0, 代入方程得到C=x0y1x1y0
    然后回到问题, 设两条直线分别是

    l1:a1x+b1y+c1=0,l2:a1x+b1y+c2=0.

    这样(x,y)就是联立得到方程组的解.
    根据相关知识, 我们可以得到
    x=b2c1b1c2b1a2b2a1y=a1c2a2c1b1a2b2a1

    而这些量都可以用叉积的形式表示出来..
    所以就可以这么写:

     inline vec lineCutLineNode(const vec &p0,const vec &p1,const vec &q0,const vec &q1){
        double a1,b1,c1,a2,b2,c2,d;
        a1=p1.y-p0.y; b1=p0.x-p1.x; c1=p0*p1;
        a2=q1.y-q0.y; b2=q0.x-q1.x; c2=q0*q1;
        d=a1*b2-a2*b1;
        return vec((b2*c1-b1*c2)/d,(a1*c2-a2*c1)/d);
     }

    这种方法略微麻烦, 但是精度比较好.
    另一种方法是利用叉积的比值.

    ​ 利用参数方程的知识(数学 选修2-1 今天刚学哒XD), 我们可以用一个定点和一个方向向量确定一条直线.
    ​ 令两条直线

    l1=P+tv⃗ ,l2=Q+tw⃗ 

    ​ 然后因为是交点, 所以同时满足两条直线的方程, 令交点在两条直线的参数分别为λμ, 则有
    P+λv⃗ =Q+μw⃗ 

    ​ 两边分别叉乘w⃗ 
    (P+λv⃗ )×w⃗ =(Q+μw⃗ )×w

    ​ 去括号,
    P×w⃗ +λv⃗ ×w⃗ =Q×w⃗ +μw⃗ ×w⃗ 

    ​ 又w⃗ 一定与自己共线, 所以w⃗ ×w⃗ =0, 这样就没有\mu了, 移项, 合并同类项,
    (QP)×w⃗ =λv⃗ ×w⃗ 

    ​ 系数除到另一边去, 得
    λ=(QP)×w⃗ v⃗ ×w⃗ 

​ 那么这样就都是已知量了, 代入进去即可.
​ 最后要求的交点就是P+λv⃗ 啦~

 留一道例题:[poj1269](http://poj.org/problem?id=1269)

完整的代码在github里面哟~

相关标签: 计算几何