算法篇—- 几何算法

  • Post author:
  • Post category:其他




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

/*两点确定直线方程

  • A = Y2 – Y1
  • B = X1 – X2
  • C = X2

    Y1 – X1

    Y2

    */

    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;

    }

  3. 向量

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

    3.2 内积(点积) A·B = |A||B|cos©

    (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©

    (1) 几何意义:向量A与B张成的平行四边形的有向面积。B在A的逆时针方向为正。

    (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;

否则是四个端点分别到线段的距离中取最小值。



(11)多边形面积

我们利用的是叉乘的几何意义是两向量构成的平行四边形的面积。(除以2就是三角形的面积)

通过与第一个点来将n边形分割成n-2个三角形的和,而每一个三角形面积都可以直接由向量叉乘求得。如下图

我们按照逆时针顺序来求,那么角度都是正的,a 叉乘 b ,结果的正负是由a到b这个的角度的sin决定的。



(12)凸边形(判断)

如果把多边形的边顺序按同一侧作为起点来看,可以发现,凸多边形的边都按同一角度选择着

根据这一规律,我们对边的向量进行叉乘,便可判断

叉乘的几何意义是 一个垂直于两个向量的方向, 如果所有边依次相乘的方向都是一样的, 就是凸多边形, 此题因为规定了必须逆时针, 所以所有相乘都必须是正的(这个可以自己试一下就知道)



(13)点是否在多边形内部

如果是判断在凸边形内部的话,可以依据上面的规律,判断该点依次分别与两个点的叉乘都是正的也就是在左边(逆时针)

在计算几何中,判定点是否在多边形内,是个非常有趣的问题。通常有两种方法:

1.Crossing Number(交叉数)

它计算从点P开始的射线穿过多边形边界的次数。当“交叉数”是偶数时,点在外面;当它是奇数时,点在里面。这种方法有时被称为“奇-偶”检验。

2.Winding Number(环绕数)

它计算多边形绕着点P旋转的次数。只有当“圈数”wn = 0时,点才在外面; 否则,点在里面。

详细解读:https://blog.csdn.net/u013279723/article/details/106265948



(14)求凸包

首先讲解一下凸包的概念

用比较抽象的说就是:

在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。X的凸包可以用X内所有点

(X1,…Xn)的凸组合来构造.

简单来说:

给你一个点集Q,你可以把Q中的每个点想象成一块木板上的铁钉,而点集Q的凸包就是包围了所有铁钉的一条拉紧

了橡皮绳所构成的形状;

算法:安德鲁算法(Andrew)

在这之前我们要明白一个凸包的重要性质:在这些点集中,x最小,最大,y最小最大,这四个极点,肯定是在凸包上面的,x最小和最大,可以将凸包分为上凸包和下凸包,y最小与最大可以将凸包分为左凸包和右凸包。这里我们是按照左凸包和右凸包来求的。

因此在求之前需要对点进行排序,y小的放前边,y相同的话,x小的放前边。

bool operator <(const point &c)const {
			if (y < c.y)
				return true;
			if (y > c.y)
				return false;
			if (x < c.x)
				return true;
			return false;
		}

我们先从y最小开始求出右凸包,什么判断点是在凸包上呢,利用叉乘,新的点应该与最近的两个维护的凸点的叉乘不能小于0(也就是三个点)

因此这就为什么说如果我们最后的结果要包含边上的话就是< , 因为如果是边界的话是0(平行),我们不应该排除掉它

依次我们在求到y最大的点的时候,就维护好了右凸边,然后我们再从y最高到y最小来维护出左凸边,因为最后起点会重复加入,减去1即可得到答案。

// 求凸包,返回的是凸包的顶点数 ,ch 中存的就是对应的顶点

int ConvexHull(point *p, int n, point *ch) {
	sort(p, p + n);
	int m = 0;
	for (int i = 0 ; i < n ; ++i) {
		while (m > 1 && sign(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) {
		// p[i] - ch[m - 2] 是 p[i] - ch[m - 1] 也可以!
		while (m > k && sign(cross(ch[m - 1] - ch[m - 2], p[i] - ch[m - 2]) ) < 0)
			m--;
		ch[m++] = p[i];
	}
	if (n > 1)
		--m;
	return m;
}



(15)求凸包的直径(平面最远点对)

前置知识:

1、被一对卡壳正好卡住的对应点对称为对踵点,对锺点的具体定义不好说,不过从图上还是比较好理解的。

2、可以证明对踵点的个数不超过3N/2个 也就是说对踵点的个数是O(N)的

3、我们需要对点集先求出凸包的点集(这里不要边上的点)

如果qa,qb是凸包上最远两点,必然可以分别过qa,qb画出一对平行线。通过旋转这对平行线,我们可以让它和凸包上的一条边重合,如图中蓝色直线,因此依据这个我们就可以快速求出对踵点 ,与对踵点与边重合的两个点构成两个向量叉乘的结果< 0 这样才会有两条平行线不经过多边形内部将它们卡住

可以注意到,qa是凸包上离p和qb所在直线最远的点。于是我们的思路就是枚举凸包上的所有边,对每一条边找出凸包上离该边最远的顶点,计算这个顶点到该边两个端点的距离,并记录最大的值。

直观上这是一个O(n2)的算法,和直接枚举任意两个顶点一样了。

然而我们可以发现 凸包上的点依次与对应边产生的距离成单峰函数(如下图:)

根据这个凸包的特性,我们注意到逆时针枚举边的时候,最远点的变化也是逆时针的,这样就可以不用从头计算最远点,而可以紧接着上一次的最远点继续计算。于是我们得到了O(n)的算法。这就是所谓的“旋转”吧!

利用旋转卡壳,我们可以在O(n)的时间内得到凸包的对锺点中的长度最长的点对。

又由于最远点对必然属于对踵点对集合 ,那么我们先求出凸包 然后求出对踵点对集合 然后选出距离最大的即可

double RotaingCalipers(point *ch, int m) {
	if (m == 1)
		return 0.0;
	else if (m == 2)
		return dis2(ch[0], ch[1]);
	double ret = 0.0;
	//j就是对踵点
	for (int i = 0, j = 1; i < m ; ++i) {
		while (cross(ch[i + 1] - ch[i], ch[j] - ch[i]) < cross(ch[i + 1] - ch[i], ch[j + 1] - ch[i]))
			j = (j + 1) % m;
		ret = max(ret, get_length(ch[j] - ch[i]));
	}
	return ret;
}



(16)三角形

在C语言中 sin 的参数是 弧度,而不是角度. 所有我们计算一个角度的sin 值时,应先转成弧度值.

弧度 = 角度 * 3.1415926 / 180.0

同时为了确保精度,PI要详细一些:#define PI 3.1415926535897



版权声明:本文为qq_39838607原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。