算法进阶--- 几何算法篇(一)

364 阅读8分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第16天,点击查看活动详情

1)知道两个点,求直线方程。

两点确定直线方程

  • A = Y2 - Y1
  • B = X1 - X2
  • C = X2Y1 - X1Y2

AX + BY + C = 0 ;

是由 y = kx + c , k = (y2 - y1)/(x2-x1) , 再将一个点带入求的c后进行化简之后的结果

(2)求点到直线的距离

利用叉乘来求。三个点,一个是目标点,构造两个向量。分别是直线向量和目标点于直线上一点。这两个向量叉乘得到的向量的模,的几何意义是以这两个向量为领边的平行四边形的面积。因此叉乘的模除两个点构成的向量的模就得到了目标点到直线的距离。即得到四边形的面积 = 高*底

(3)向量法求一个点到一个直线的垂足。 是通过就点到直线上的投影来求的。

首先通过直线外一点与直线上一点构成的向量的点积与直线的向量的点积/向量的点积。得到 |b|cosx / |a| ,其中|b|cosx为投影。所以大概的几何意义是投影在直线上的单位长度。因此 s(直线起始点) + r(为上面所说的投影单位长度)(t-s) 得到垂足点。 r(t-s)

3)点到直线的投影

向量除以自己的模表示和该向量方向相同的方向向量

向量的点乘:(x1,y1) * (x2,y2) = x1*x2 + y1 * y2; D = B +BD

(4)点关于直线的对称点

我们知道 垂足是点与其对称点的中点,因此我们可以先求出点在直线上的中点,然后依据 ,(x + x1) = 2 * x2 , (y + y1) = 2 * y2 求出(x,y)

(5)两条直线的位置关系

叉乘(cross product) 相对于点乘,叉乘可能更有用吧。2维空间中的叉乘是: V1(x1, y1) X V2(x2, y2) = x1y2 – y1x2 看起来像个标量,事实上叉乘的结果是个向量,方向在z轴上。上述结果是它的模。在二维空间里,让我们暂时忽略它的方向,将结果看成一个向量,那么这个结果类似于上述的点积,我们有: A x B = |A||B|Sin(θ) 然而角度 θ和上面点乘的角度有一点点不同,他是有正负的,是指从A到B的角度。 所以这个就说明了为什么可以通过叉乘的结果知道同起点的另一条直线和定直线的关系,因为sin 在 0 ~ 180 为正 , 所以叉乘为正说明在逆时针方向,(因为这个角度使用定直线到动直线之间的夹角决定的)

我们得到直线的方向向量的时候是由直线上两点得到的 ,如: A = (x2 - x1,y2 - y1) ,X1 = x2 - x1 ,Y1 = y2 - y1; 1、平行:如果两个直线平行说明,两个直线的斜率(K)是相等的,那么就有 Y1/X1 = Y2/X2 ,也就是 X1*Y2 - Y1 * X2 = 0 , 不过在这个基础上我们还可以通过这个式子的正负判断两个直线的左右关系(共一个起点的时候),我们然 Y1/X1 是定值的话, 当 直线左边的时候(指的是)红色的位置,虽然在第三象限为正但是定的还是会比新的大,所以 有定的K - 新的K 是大于0的,那么就有 (Y1/X1) - (Y2/X2) > 0 也就是 , Y1 * X2 - X1 * Y2 > 0

2、垂直的时候是,两个直线的方向向量的点乘为0

(6)两个线段是否会相交

假设有两条线段AB,CD,若AB,CD相交,我们可以确定: 1.线段AB与CD所在的直线相交,即点A和点B分别在直线CD的两边; 2.线段CD与AB所在的直线相交,即点C和点D分别在直线AB的两边; 上面两个条件同时满足是两线段相交的充要条件,所以我们只需要证明点A和点B分别在直线CD的两边,点C和点D分别在直线AB的两边,这样便可以证明线段AB与CD相交了。 不过有两个特殊的情况分别是:只有一点相交

和 重合的情况

对于第一种情况如果两直线叉乘有一个为0的话也是相交的 对于第二种情况,我们需要先判断平行和共线,因为这样才有重合的情况,那么共线如何判断呢,我们在两个直线中个选一个点,连接后如果这个直线也和原本的直线平行那么就说明共线,但共线之后不一定重合,如下图

因此我们的步骤是: 1、将点分别排好序 2、如果有一个点重合(一样)那么一定相交 3、如果平行,如果共线,如果第一个直线的起点<= 第二的起点且,第一的终点>= 第二的起点 ,反之也可以,就重合(相加) 4、不平行的话,验证上面的重要条件

(7)知识点

  1. 前置知识点

    (1) pi = acos(-1); (2) 余弦定理 c^2 = a^2 + b^2 - 2abcos(t)

  2. 浮点数的比较

const double eps = 1e-8; int sign(double x) // 符号函数 { if (fabs(x) < eps) return 0; if (x < 0) return -1; return 1; } int cmp(double x, double y) // 比较函数 { if (fabs(x - y) < eps) return 0; if (x < y) return -1; return 1; }

  1. 向量

3.1 向量的加减法和数乘运算

3.2 内积(点积) A·B = |A||B|cos(C)
    (1) 几何意义:向量A在向量B上的投影与B的长度的乘积。
    (2) 代码实现
    double dot(Point a, Point b)
    {
        return a.x * b.x + a.y * b.y;
    }
3.3 外积(叉积) AxB = |A||B|sin(C)
    (1) 几何意义:向量AB张成的平行四边形的有向面积。BA的逆时针方向为正。
    (2) 代码实现
    double cross(Point a, Point b)
    {
        return a.x * b.y - b.x * a.y;
    }
3.4 常用函数
    3.4.1 取模
    double get_length(Point a)
    {
        return sqrt(dot(a, a));
    }
    3.4.2 计算向量夹角
    double get_angle(Point a, Point b)
    {
        return acos(dot(a, b) / get_length(a) / get_length(b));
    }
    3.4.3 计算两个向量构成的平行四边形有向面积
    double area(Point a, Point b, Point c)
    {
        return cross(b - a, c - a);
    }
    3.4.5 向量A顺时针旋转C的角度:
    Point rotate(Point a, double angle)
    {
        return Point(a.x * cos(angle) + a.y * sin(angle), -a.x * sin(angle) + a.y * cos(angle));
    }

4. 点与线

4.1 直线定理

    (1) 一般式 ax + by + c = 0
    (2) 点向式 p0 + vt
    (3) 斜截式 y = kx + b
4.2 常用操作
    (1) 判断点在直线上 A x B = 0
    (2) 两直线相交
    // cross(v, w) == 0则两直线平行或者重合
    Point get_line_intersection(Point p, Vector v, Point q, vector w)
    {
        vector u = p - q;
        double t = cross(w, u) / cross(v, w);
        return p + v * t;
    }
    (3) 点到直线的距离
    double distance_to_line(Point p, Point a, Point b)
    {
        vector v1 = b - a, v2 = p - a;
        return fabs(cross(v1, v2) / get_length(v1));
    }
    (4) 点到线段的距离
    double distance_to_segment(Point p, Point a, Point b)
    {
        if (a == b) return get_length(p - a);
        Vector v1 = b - a, v2 = p - a, v3 = p - b;
        if (sign(dot(v1, v2)) < 0) return get_length(v2);
        if (sign(dot(v1, v3)) > 0) return get_length(v3);
        return distance_to_line(p, a, b);
    }
    (5) 点在直线上的投影
    double get_line_projection(Point p, Point a, Point b)
    {
        Vector v = b - a;
        return a + v * (dot(v, p - a) / dot(v, v));
    }
    (6) 点是否在线段上
    bool on_segment(Point p, Point a, Point b)
    {
        return sign(cross(p - a, p - b)) == 0 && sign(dot(p - a, p - b)) <= 0;
    }
    (7) 判断两线段是否相交
    bool segment_intersection(Point a1, Point a2, Point b1, Point b2)
    {
        double c1 = cross(a2 - a1, b1 - a1), c2 = cross(a2 - a1, b2 - a1);
        double c3 = cross(b2 - b1, a2 - b1), c4 = cross(b2 - b1, a1 - b1);
        return sign(c1) * sign(c2) <= 0 && sign(c3) * sign(c4) <= 0;
    }

5. 多边形 5.1 三角形 5.1.1 面积

    (1) 叉积
    (2) 海伦公式
        p = (a + b + c) / 2;
        S = sqrt(p(p - a) * (p - b) * (p - c));
5.1.2 三角形四心
    (1) 外心,外接圆圆心
        三边中垂线交点。到三角形三个顶点的距离相等
    (2) 内心,内切圆圆心
        角平分线交点,到三边距离相等
    (3) 垂心
        三条垂线交点
    (4) 重心
        三条中线交点(到三角形三顶点距离的平方和最小的点,三角形内到三边距离之积最大的点)
5.2 普通多边形
    通常按逆时针存储所有点
    5.2.1 定义
    (1) 多边形
        由在同一平面且不再同一直线上的多条线段首尾顺次连接且不相交所组成的图形叫多边形
    (2) 简单多边形
        简单多边形是除相邻边外其它边不相交的多边形
    (3) 凸多边形
        过多边形的任意一边做一条直线,如果其他各个顶点都在这条直线的同侧,则把这个多边形叫做凸多边形
        任意凸多边形外角和均为360°
        任意凸多边形内角和为(n−2)180°
    5.2.2 常用函数
    (1) 求多边形面积(不一定是凸多边形)
    我们可以从第一个顶点除法把凸多边形分成n − 2个三角形,然后把面积加起来。
    double polygon_area(Point p[], int n)
    {
        double s = 0;
        for (int i = 1; i + 1 < n; i ++ )
            s += cross(p[i] - p[0], p[i + 1] - p[i]);
        return s / 2;
    }
    (2) 判断点是否在多边形内(不一定是凸多边形)
    a. 射线法,从该点任意做一条和所有边都不平行的射线。交点个数为偶数,则在多边形外,为奇数,则在多边形内。
    b. 转角法
    (3) 判断点是否在凸多边形内
    只需判断点是否在所有边的左边(逆时针存储多边形)。
5.3 皮克定理
    皮克定理是指一个计算点阵中顶点在格点上的多边形面积公式该公式可以表示为:
        S = a + b/2 - 1
    其中a表示多边形内部的点数,b表示多边形边界上的点数,S表示多边形的面积。

6. 圆 (1) 圆与直线交点 (2) 两圆交点 (3) 点到圆的切线 (4) 两圆公切线 (5) 两圆相交面积

(8)两个线段的交点

利用叉乘表示有效面积 和 相似三角形 来进行计算 ; 同时在写的时候 , 在叉乘相除的时候,注意顺序要确保角的方向是一致的。

(9)点到线段之间的距离

因此,如果 直线是一个点的 的话就 直接是 a 到 b 的jul 否则 就可以 通过 直线的方向向量与ab的点乘判断是哪一种情况(点乘可以判断角度) 如果是正的话就是,点到直线的距离否则是两点之间的距离

(10)两条线段之间的距离

广义的定义是:空间两直线上的点之间的最短距离;

如果线段相交那么 ,距离为0; 否则是四个端点分别到线段的距离中取最小值。